Limits
Eventkiwi applies two independent limits: a monthly event allowance tied to your plan, and a request rate limit that protects the service from bursts. They are enforced separately, so it is possible to be within one and blocked by the other.
Plan Limits
Every account has an event allowance. The free allowance is a one-time trial, not a monthly one — this is the most common surprise, so it is worth being precise about.
| Plan | Event allowance | Resets? | Price |
|---|---|---|---|
| Free | 100 events total | Never — one-time trial | $0 |
| Pro | 10,000 events | Every billing period | $19/mo |
| Business | 50,000 events | Every billing period | $59/mo |
The free trial is one-time
The 100 free events are counted for the lifetime of your account. They are not 100 per month, and they do not roll over or refill. Once you have sent 100 events, further events are rejected until you upgrade to a paid plan.
The trial is sized to let you wire up an aggregator, send real events, and receive an actual report — enough to judge whether Eventkiwi is useful to you.
Paid plans reset each period
On Pro and Business the allowance refills at the start of each billing period.
The periodStart and periodEnd fields in the error response tell you exactly
when.
What happens when you run out
Events sent after the allowance is used up are rejected with a 403. Nothing
you have already collected is lost, and your aggregators, schedules and channels
are left untouched — reporting continues on whatever data you had. We also send
you a one-off email the first time you hit the limit, so it does not fail
silently.
Plan Limit Exceeded Response
Exceeding your allowance returns 403 Forbidden:
{
"error": "Plan limit exceeded",
"currentCount": 100,
"limit": 100,
"remaining": 0
}
periodStart and periodEnd are included on paid plans, which reset each
billing period. They are absent on the free trial, which has no period.
The same values are returned as headers on the 403 response:
| Header | Description |
|---|---|
X-PlanLimit-Current | Events used in the current period |
X-PlanLimit-Limit | Total events allowed per period |
X-PlanLimit-Remaining | Events remaining in the current period |
What to do:
- On the free trial, upgrade to a paid plan — the trial does not refill
- On a paid plan, check
periodEndto see when your allowance resets - Send only the events you actually report on, rather than every event you have
Checking your usage
Your current usage is shown on your dashboard and on the Plan Settings page in the app. Free trial accounts show the total used against the one-time 100; paid accounts also show the date the allowance renews.
Rate Limits
API Request Rate
5 requests per second per user account
This limit applies to event submission (/api/event). It is checked after your
API key and aggregator ID are validated, so a request with bad credentials gets
a 401 or 404 rather than a 429.
How It Works
The rate limit uses a sliding window algorithm:
- Limit resets continuously over a 1-second window
- If you exceed the limit, requests are rejected until the window slides forward
- The limit applies per user account (not per aggregator)
Rate Limit Exceeded Response
When you exceed the rate limit, you'll receive a 429 Too Many Requests response:
{
"error": "Rate limit exceeded",
"limit": 5,
"remaining": 0,
"resetTime": 1640995201000
}
resetTime is a Unix timestamp in milliseconds, so it can be compared with
Date.now() directly.
The 429 response also carries the same values as headers:
| Header | Description |
|---|---|
X-RateLimit-Limit | Total requests allowed per second |
X-RateLimit-Remaining | Requests remaining in current window |
X-RateLimit-Reset | Unix timestamp in milliseconds when the window resets |
These headers are only sent on a 429. Successful responses do not include
them, so you cannot read your remaining budget ahead of time — pace your
requests instead (see Best Practices).
What to do:
- Wait until
resetTime - Implement exponential backoff in your code
- Reduce request frequency
- Contact us if you need higher limits
Other Limits
Event Name Length
Event names must be URL-compatible and not cause HTTP 414 (URL Too Long) errors.
Practical limit: Keep event names under 200 characters.
Tag Limits
- Maximum tag key length: 100 characters
- Valid characters: Letters, numbers, hyphens, underscores and colons (after normalization)
- Tags per event: no enforced limit
Tags exceeding the length limit are silently ignored; the event itself is still recorded. There is no cap on how many tags an event carries, but every unique tag becomes a line in your reports, so keep it to a handful. See Event Tags.
Aggregator Limits
There are no hard limits on the number of aggregators you can create per account.
Best Practices
1. Batch Events When Possible
Instead of sending events immediately, batch them:
// Instead of this (5+ requests/sec):
for (const event of events) {
await sendEvent(event.name);
}
// Do this (controlled rate):
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
for (const event of events) {
await sendEvent(event.name);
await delay(250); // 4 requests/second
}
2. Implement Retry with Exponential Backoff
Handle rate limit errors gracefully:
async function sendEventWithRetry(name, tags, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
const response = await fetch(
`https://app.eventkiwi.com/api/event?_id=${AGGREGATOR_ID}`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ name, ...tags })
}
);
if (response.status === 429) {
// Rate limited - wait until the window resets, then retry.
// resetTime is already in milliseconds.
const data = await response.json();
const waitTime = Math.max(0, data.resetTime - Date.now());
await new Promise(resolve => setTimeout(resolve, waitTime));
continue;
}
return await response.json();
} catch (error) {
if (i === maxRetries - 1) throw error;
// Exponential backoff
await new Promise(resolve =>
setTimeout(resolve, Math.pow(2, i) * 1000)
);
}
}
}
3. Don't Retry Plan Limit Errors
A 429 clears within a second. A 403 with "error": "Plan limit exceeded"
does not: it lasts until your allowance resets or you upgrade. Retrying it only
burns rate limit, so log it and stop:
if (response.status === 403) {
const data = await response.json();
if (data.error === 'Plan limit exceeded') {
console.error('Eventkiwi allowance used up', data);
return; // don't retry
}
}
4. Use Queues for High-Volume Applications
For applications with bursts of events:
class EventQueue {
constructor() {
this.queue = [];
this.processing = false;
}
add(name, tags) {
this.queue.push({ name, tags });
if (!this.processing) {
this.process();
}
}
async process() {
this.processing = true;
while (this.queue.length > 0) {
const event = this.queue.shift();
await sendEvent(event.name, event.tags);
// Wait 250ms between requests (4/sec, safe margin)
await new Promise(resolve => setTimeout(resolve, 250));
}
this.processing = false;
}
}
const eventQueue = new EventQueue();
// Usage
eventQueue.add('user_signup', { 'env-production': true });
Need Higher Limits?
If your application requires higher rate limits, we'd love to hear from you!
Contact Us
- Email: info@eventkiwi.com, subject "Rate Limit Increase Request"
- X/Twitter: @d3liaz
Please include:
- Your account email
- Current usage patterns
- Expected request volume
- Use case description
We'll work with you to find a solution that meets your needs.
Monitoring Your Usage
Your event usage against your plan allowance is shown on your dashboard and on the Plan Settings page, along with the renewal date on paid plans.
Successful responses carry no usage headers, so from your own application:
- Log 429 and 403 responses to see which limit you are hitting — the
X-RateLimit-*andX-PlanLimit-*headers arrive only on those responses - Watch your inbox — you get one email the first time your allowance runs out
Next Steps
- Authentication - Set up API authentication
- Sending Events - Learn how to send events
- Event Tags - Add metadata to events