Skip to main content

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.

PlanEvent allowanceResets?Price
Free100 events totalNever — one-time trial$0
Pro10,000 eventsEvery billing period$19/mo
Business50,000 eventsEvery 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.

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:

HeaderDescription
X-PlanLimit-CurrentEvents used in the current period
X-PlanLimit-LimitTotal events allowed per period
X-PlanLimit-RemainingEvents remaining in the current period

What to do:

  1. On the free trial, upgrade to a paid plan — the trial does not refill
  2. On a paid plan, check periodEnd to see when your allowance resets
  3. 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:

HeaderDescription
X-RateLimit-LimitTotal requests allowed per second
X-RateLimit-RemainingRequests remaining in current window
X-RateLimit-ResetUnix timestamp in milliseconds when the window resets
note

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:

  1. Wait until resetTime
  2. Implement exponential backoff in your code
  3. Reduce request frequency
  4. 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​

Please include:

  1. Your account email
  2. Current usage patterns
  3. Expected request volume
  4. 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:

  1. Log 429 and 403 responses to see which limit you are hitting — the X-RateLimit-* and X-PlanLimit-* headers arrive only on those responses
  2. Watch your inbox — you get one email the first time your allowance runs out

Next Steps​