Rate limits, quotas and caching to scale a BCE/KBO application: reference architecture
Your app queries the BCE/KBO API at volume — how to avoid 429s, control costs and guarantee latency? Reference architecture with multi-layer cache, request queue, observability and webhooks. Concrete patterns and target numbers.
In brief
The Company Belgium BCE/KBO API exposes rate-limit headers your code can read directly. To scale without exhausting your quota, combine a 4-layer cache (CDN, memory, shared cache, DB snapshot) with webhooks for continuous tracking. This architecture absorbs 95 % of traffic before the API and keeps latency under 50 ms.
The problem: going from 100 to 100,000 requests/day
You start with a few calls a day. The product grows. Six months later, your app does 100,000 lookups daily. If you call the API on every page view by every user, you:
- Saturate your quota
- Receive 429 Too Many Requests
- Pay the volume bill
- Increase user-side latency
The architecture in this article prevents that. It's production-tested.
Understanding rate limits
Anatomy of a window
An API typically exposes a quota per sliding window:
- Free plan: 10 req/min, 100 req/day
- Starter plan: 60 req/min, 5,000 req/day
- Pro plan: 600 req/min, 100,000 req/day
- Enterprise plan: per contract
Company Belgium headers:
1 2 3 4
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 432
X-RateLimit-Reset: 1746541234
Retry-After: 30 (only on 429)
X-RateLimit-Reset is a Unix timestamp: at that second, the quota resets.
The 3 limit types
A good app must handle all three, not just the occasional 429.
Reference architecture in 4 layers
1 2 3 4 5 6 7 8 9 10 11
[ User Request ]
↓
[ Browser cache + CDN ] ← Layer 1 (free, outside quota)
↓
[ App memory cache (LRU TTL) ] ← Layer 2 (10-100 ms)
↓
[ Shared cache (memcached) ] ← Layer 3 (1-10 ms network)
↓
[ Database snapshot ] ← Layer 4 (10-50 ms, freshness ~24h)
↓
[ BCE/KBO API ] ← Quota & cost
Goal: less than 5 % of user requests actually hit the API in layer 5.
Layer 1 — CDN / Browser
For public endpoints without sensitive data, expose your proxy routes with Cache-Control: public, max-age=300, s-maxage=600. CloudFront, Cloudflare, Vercel Edge Network do the rest.
Typical effect: 50 to 70 % of requests absorbed before your app.
Layer 2 — App memory cache
LRU with TTL (see the Python tutorial and Node.js tutorial):
- Capacity: 500 to 5,000 records based on RAM
- TTL: 5 min for records, 30 min for NACE codes
- Eviction: LRU
Typical effect: +20 % absorption.
Layer 3 — Shared cross-process cache
If you have multiple app instances (Docker, Kubernetes, Vercel functions), a local cache is not shared. Use:
- Memcached: simple, fast, shared-nothing
- In-cluster cache like Caffeine (Java), Tinybird (analytics)
- Postgres with a
cachetable + key index (enough for typical SaaS volume)
Effect: a MISS on instance A can be a HIT on layer 3, populated by instance B 10 seconds earlier.
Layer 4 — DB snapshot
For apps that do many lookups on their own clients (CRM, ERP, accounting), maintain a local copy of BCE records for tracked companies. Synchronized via webhooks.
Pros:
- Local queries: 10-50 ms
- No network dependency on reads
- SQL filter/group available
Cons:
- Data possibly hours old
- One more table to maintain
The right tradeoff for 80 % of B2B SaaS cases.
Layer 5 — Direct API call
When all previous layers miss, call the API. Wrapped with:
- Exponential retry on 429/5xx honoring
Retry-After
- Circuit breaker: if > 50 % errors over 1 minute, stop calls for 30 seconds
- Hard timeout (10 s)
- Local limit: semaphore of max N concurrent requests
Strategies per use case
A. Supplier verification (infrequent, must be fresh)
- Memory cache 1 minute
- No CDN (client-specific)
- Direct call on miss
Typical API volume: 1 req per file.
B. Public directory display (frequent, can be slightly stale)
- CDN 30 minutes
- Memory cache 5 min
- DB snapshot refreshed nightly
- Direct call only on full miss
Typical API volume: 1 req per 100 views.
C. Full-text search (frequent, must be fresh)
- No CDN (unique query URLs)
- Memory cache 1 min with key =
hash(query+filters)
- Direct call on miss
Typical API volume: 1 req per 5-10 searches.
D. Monitoring/alerts (continuous, event-driven)
- No polling — webhooks
- DB snapshot kept current via webhooks
- Memory cache optional
Typical API volume: 0 req per day (all via webhooks).
Webhooks vs polling: the math
You track 1,000 clients and want to detect changes within 15 minutes.
Polling:
- 1,000 companies × (60/15) lookups/h × 24h = 96,000 req/day
- Pro plan required (100,000/day)
- Cost: ~€300/month
Webhooks:
- ~20 changes/day (observed rate)
- 20 webhooks received = 20 optional lookups = 20 req/day
- Free plan enough
- Cost: €0/month
The gap is 4,800×. Webhooks are almost always the right answer for monitoring.
Handling 429s properly
Pseudo-code for smart retry:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
function callApi(req):
attempt = 0
while attempt < 4:
res = http.send(req)
if res.status == 429:
delay = res.header("Retry-After") || base * 2^attempt
sleep(delay + jitter())
attempt++
continue
if res.status in [500, 502, 503, 504]:
sleep(base * 2^attempt + jitter())
attempt++
continue
return res
throw RateLimitExhausted()
Jitter = random ±25 % delay to avoid the thundering herd (all instances retrying at once).
Observability
Metrics to expose (Prometheus / Datadog / OpenTelemetry):
| Metric | Type | Why |
|---|---|---|
bce_api_requests_total{status} | counter | Volume per HTTP code |
bce_api_request_duration_ms | histogram | Latency p50/p95/p99 |
bce_api_rate_limit_remaining | gauge | Remaining quota (alert < 10 %) |
bce_cache_hits_total{layer} | counter | Per-layer effect |
bce_circuit_breaker_state | gauge | 0 closed / 1 half / 2 open |
Alert when X-RateLimit-Remaining < 10 % → fail over to DB snapshot.
Capacity table
| User volume | Minimal strategy | API quota required |
|---|---|---|
| < 1,000 req/day | Direct calls + retry | Free |
| 1 K - 10 K req/day | + memory cache | Starter |
| 10 K - 100 K req/day | + shared cache + CDN | Pro |
| 100 K - 1 M req/day | + DB snapshot + webhooks | Pro or Enterprise |
| > 1 M req/day | All + cache sharding | Enterprise + dedicated SLA |
Common pitfalls
"0200.065.765" and "0200065765" must share the same keysingleflight to group identical in-flight requestsHow Company Belgium helps you scale
- Transparent rate-limit headers: your code adapts
- Webhooks for 9 event types (
company.updated,company.ubo.changed,company.peppol.registered, etc.) — see endpoints reference
- Plan tiers aligned with real architectures (Free, Starter, Pro, Enterprise)
- Sandbox to test without consuming production quota
- Bulk endpoint on pro plans (100 records per call)
- Functional comparison with other APIs: 2026 comparison and official vs third-party APIs
Bottom line
Scaling a BCE/KBO app is not just upgrading to a bigger plan. The right architecture absorbs 95 % of traffic before the API, via 4 cache layers and webhooks. The quota becomes a formality, latency stays under 50 ms, and the bill follows value, not noise.
Three levers to activate in order: memory cache + retry, webhooks instead of polling, DB snapshot for your own clients. With that, you serve 100 K user req/day consuming only 5 K API req/day.
Frequently asked questions
What is a rate limit on the Company Belgium BCE/KBO API?
A rate limit caps the number of requests an application can send to the API within a given time window. On Company Belgium, limits are expressed per minute and per day depending on the plan: 10 req/min on the free plan, 600 req/min on the Pro plan. The X-RateLimit-Remaining and Retry-After headers inform you in real time about your quota status.
How do you avoid 429 Too Many Requests errors with the BCE/KBO API?
Implement a multi-layer cache to absorb repeated requests, and an exponential retry with jitter for requests that still hit the limit. Using webhooks instead of polling dramatically reduces call volume, often by a factor of 4,800. Also separate your API keys between production and batch environments to avoid contention.
What caching strategy is recommended for a Belgian B2B SaaS app consuming the KBO API?
The recommended architecture combines 4 layers: a CDN cache for public endpoints (TTL 5-10 min), a per-instance LRU memory cache (TTL 5 min), a shared Memcached or Postgres cache across instances, and a local DB snapshot for companies you track. This combination absorbs 95 % of user traffic before the API and reduces both cost and latency.
Webhooks or polling for monitoring BCE/KBO changes in 2026?
Webhooks are almost always preferable. With polling every 15 minutes across 1,000 companies, you consume roughly 96,000 requests per day. With Company Belgium webhooks, you only receive actual events, averaging about 20 notifications per day for the same coverage. The consumption gap is a factor of 4,800, often allowing you to stay on the free plan.
Comments
Related articles

Belgian Crossroads Bank for Enterprises (BCE/KBO) API: complete English developer documentation
English developer documentation for the Belgian Crossroads Bank for Enterprises API. Overview of the BCE/KBO registry, legal context, access options, quick start with cURL examples, and integration patterns for international developers.

BCE/KBO API endpoints reference: complete list of REST routes (companies, addresses, NACE, UBO, establishments, Peppol)
Comprehensive technical reference of the Company Belgium REST API endpoints: search, company record, addresses, NACE activities, establishments, directors, UBO, Peppol verification. With response schemas, parameters and error codes.

Official KBO API vs third-party APIs: limitations of the public BCE service and why move to a modern API
The official SPF Economie API is free — but it's SOAP, without webhooks, without UBO or Peppol enrichment, and without SLA. Here is why 80% of serious projects end up migrating to a third-party API, and how to evaluate if it's your case.
