Rate limits
Limits apply per project (not per token, not per user, not per IP) and are shared across every endpoint — the quota is one pool, so calls to/v1/messages and /v1/crm/tickets draw from the same budget.
The three windows stack, and the tightest one wins. The daily cap is the one that matters: at a steady pace it is roughly 40 requests per hour, which is what the hourly window is sized around. The minute window lets a batch job move at 60 a minute for a short stretch; the hourly window is what stops that stretch from spending the whole day in a few minutes.
If you cross a limit:
The MCP server is outside these limits. Tool calls on
POST /v1/mcp are not counted against the per-minute or per-day quota. That is convenient and also a footgun: an agent stuck in a loop generates real upstream traffic with nothing stopping it, so cap it on your side.Read the headers instead of waiting for the 429
Every response carries the state of each window, so you can self-throttle before you get blocked:
The same trio exists for
-minute and -hour. A healthy integration watches X-RateLimit-Remaining-day and slows itself down as it approaches zero, rather than sprinting into a wall.
Every window is rolling, not aligned to a clock boundary: each one opens on your first request and closes a minute, an hour or 24 hours later.
What does not count
Requests to/health are exempt. Broadcasts count as one request each, not one per recipient — POST /v1/messages/bulk with 5,000 recipients costs a single unit, which makes it far cheaper than looping over POST /v1/messages.
How to handle 429 properly
Retrying a minute-window429 and retrying a day-window 429 are different problems. Exponential backoff over a few seconds clears the first and does nothing for the second — if your daily budget is gone, no amount of waiting inside the request path will bring it back.
Read Retry-After-<window> and decide:
Staying under 1,000 a day
Prefer webhooks over polling
A poll every minute is 1,440 requests a day — over budget before you send anything. Subscribe to events and let Keebai push instead.
Batch your sends
One bulk broadcast costs one request regardless of recipient count. A loop over 500 contacts costs 500.
Cache what rarely changes
Channels, templates, branches, and services change on a human timescale. Fetch them once at boot, not once per message.
One project per workload
The quota is per project, so tokens are not how you isolate. Give a noisy batch job its own project and it cannot starve your transactional path.
Error format
Every error is wrapped in the same envelope, with a stable machine-readablecode and, on some errors, a details object:
code, never on message — the wording can change, the slug will not.
Best practices
Idempotency
Design retries to be safe.
GET and DELETE are idempotent out of the box; for POST/PATCH, dedupe on your side with stable identifiers.Sensible timeouts
Set a client timeout between 10 and 30 seconds. Lower kills legitimate requests; higher locks your thread on transient slowness.
Log everything useful
Log status, request id (when the API returns one in headers), and duration. Future-you will thank you.
Circuit breaker
On repeated 5xx, pause requests for a few seconds. Your system stays up for everything else that doesn’t depend on Keebai.
In production
- Monitor latency, error rate, and status codes. An alert on error rate
>5%catches issues before users do. - Rotate tokens every 90 days, incident or not. Mint, deploy, validate, revoke the old.
- Audit Last used in the tokens table periodically. Tokens unused for months are revoke candidates.
- Suspect us? Check status.keebai.com first — it reports availability per component and any open incident, and it runs outside our cluster so it stays up when the API does not. If nothing is reported there, email support@keebai.com.