The Latest API Clients Deliver More Speed with Fewer Errors
Storyblok is the first headless CMS that works for developers & marketers alike.
In version 0.9.1, both @storyblok/api-client and @storyblok/management-api-client deliver up to three times faster story fetches (up to 20 times faster in extreme cases) and make aborting on rate limit errors a thing of the past.
The Content Delivery and Management API clients offer improved rate limit handling natively and feature a new API for custom rate-limiting logic. The updates that unlock this performance boost are entirely under the hood with no breaking changes.
Update your dependencies now and keep reading to learn how we did it.
In numbers
The new client version can be up to three times faster, depending on circumstances. The data below represents fetching 3,818 stories from Storyblok’s website (warm cache):
Total | Single-story rate | |
|---|---|---|
Before | 84.3s | 50 requests/s |
After | 27.3s | 338 requests/s peak |
To maximize the rate limit, the new client can adjust the request rate rather than setting a fixed 50 requests per second.
Similar improvements are available when running whole fleets of clients, with better rate-limiting logic that results in far fewer responses with status code 429 (Too Many Requests):
Workers | Rate limiter | Progress | 429s | Time |
|---|---|---|---|---|
4 | Old | 56.4% | 9,928 | 48.1s |
4 | New default | 100% | 272 | 92.6s |
The above table illustrates how four parallel API clients perform with the new default rate-limiting logic. The old clients aborted after receiving too many 429 errors (over 9,000), while the fleet of new clients kept working, causing far fewer errors.
Only a large volume of HTTP responses with status 429 causes an API client to give up and raise a rate limit error. Occasional 429s are gracefully handled under the hood.
Using custom rate-limiting logic can lead to even greater gains:
Workers | Rate limiter | Progress | 429s | Time |
|---|---|---|---|---|
4 | New default | 100% | 272 | 92.6s |
4 | Custom, Redis-based | 100% | 71 | 80.2s |
These improvements result from the clients’ new cache-handling logic while offering better coordination of request rates when part of a fleet.
Dynamic, cache-aware request ceilings
The Content Delivery API has tiered rate limits:
- Single content entries: 50 per second
- Listing requests with fewer than or equal to 25 entries: 50 per second
- Listings with 25-50 entries: 15 per second
- Listings with 50-75 entries: 10 per second
- Listings with 75-100 entries: 6 per second
The limits only apply to requests that reach the origin. Any request that hits the CDN cache doesn’t count toward the tiered rate limits, but @storyblok/api-client previously didn’t account for this fact. It instead paced itself against the tiered limits to avoid overrunning them, treating all requests as counting towards the limits. This approach established a safe upper bound for the request rate but left a lot on the table.
Now, the API client accounts for the cache status of requested resources. It keeps a rolling window of the last 50 responses with an identifiable cache status via X-Cache header and uses a dynamic ceiling of tierLimit / (1 - hitShare). This yields a theoretical rate of up to 1,000 requests per second, which is the general rate limit for cached CDN responses.
As a result, a warm cache can handle much higher throughput, while cold and mixed workloads remain the same.
This improvement only applies to the Content Delivery API—the Management API is uncached and therefore subject to the same rate limits and behaviors as before.
Adaptive custom rate limit logic
The rate limit applies per API access token, which therefore acts as a shared resource if you run multiple clients in parallel. A whole fleet of clients can quickly overrun the API’s rate limit, even if every instance tries to pace itself.
There are two ways to handle this use case. The default approach works like TCP congestion control. When a client receives a response with a status code 429, it divides the request rate and then generally increases it. This forces multiple clients to share the rate limit.
To operate multiple clients consistently at the edge of the shared token’s rate limits, set a fleet-wide dynamic ceiling with custom rate-limiting logic. The following snippet is a minimal example that uses rateLimit.limiter to configure Redis as an external, shared limiting mechanism:
import { createApiClient } from "@storyblok/api-client";
import { Redis } from "ioredis";
const redis = new Redis(process.env.REDIS_URL);
redis.defineCommand("reserveSlot", {
/* ... */
});
const fleetClient = createApiClient({
accessToken,
rateLimit: {
limiter: {
// bucket = rate-limit tier, limit = that tier's req/s
async acquire({ bucket, limit }) {
const waitMs = await redis.reserveSlot(
`storyblok-ratelimit:${bucket}`,
1000 / limit,
);
if (waitMs > 0) {
await new Promise((r) => setTimeout(r, waitMs));
}
},
},
},
}); This setup ensures that every client is aware of the fleet’s overall limit and can pace themselves without first having to encounter a 429.
Upgrade now
The new clients introduce no breaking changes—upgrade now and get a risk-free speedup.
If you come across any bugs or have ideas for new features, open an issue on GitHub and let us know. Join our Discord server to tell us what you think about the new API client and to explore other Storyblok topics with members of our community.






