Rate limits
How many requests a workspace can make, the headers that say where it stands, and how to wait when the API asks you to slow down.
The limits
Rate limits keep the API fast for everyone. They are per workspace, across all of its API keys and its MCP server. When a workspace goes over them, the API answers 429 Too Many Requests with the code rate_limited.
Higher limits are available on request: tell our sales team at sales@nutalk.ai your workspace's name and what you are building. We raise them for your workspace, with no change on your side: the headers below always show your workspace's own limits.
Calls have their own limits from your plan (simultaneous calls and calls per hour): POST /v1/calls queues a call until a line is free, and campaigns respect the same limits plus their own rate_limits. Starting a test run, re-scoring one and re-reading a knowledge source share a separate allowance of 30 per hour.
Rate limit headers
Responses that reach the rate limiter carry these headers (a 401 or 403 is answered before it and has none), with your workspace's own limits. RateLimit-* and X-RateLimit-* describe the hourly window, the -Minute ones the current minute. A 429 also carries Retry-After: the seconds until every window that ran out resets. Wait that long before retrying.
| Header | What it says |
|---|---|
RateLimit-Limit | Requests allowed in the hourly window. |
RateLimit-Remaining | Requests left in the window. |
RateLimit-Reset | When the hourly window resets, as a Unix timestamp (seconds). |
X-RateLimit-Limit | The same as RateLimit-Limit, for older clients. |
X-RateLimit-Remaining | The same as RateLimit-Remaining. |
X-RateLimit-Reset | The same as RateLimit-Reset. |
X-RateLimit-Limit-Minute | Requests allowed in the current minute. |
X-RateLimit-Remaining-Minute | Requests left in the current minute. |
X-RateLimit-Reset-Minute | When the minute resets, as a Unix timestamp (seconds). |
Retry-After | On a 429 only: seconds to wait before retrying. |
HTTP/1.1 200 OK Content-Type: application/json RateLimit-Limit: 1000 RateLimit-Remaining: 987 RateLimit-Reset: 1706198400 X-RateLimit-Limit: 1000 X-RateLimit-Remaining: 987 X-RateLimit-Reset: 1706198400 X-RateLimit-Limit-Minute: 100 X-RateLimit-Remaining-Minute: 96 X-RateLimit-Reset-Minute: 1706195043
When you hit the limit
The answer says how long to wait. Retrying sooner fails the same way.
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
RateLimit-Limit: 1000
RateLimit-Remaining: 812
RateLimit-Reset: 1706198400
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 812
X-RateLimit-Reset: 1706198400
X-RateLimit-Limit-Minute: 100
X-RateLimit-Remaining-Minute: 0
X-RateLimit-Reset-Minute: 1706195043
Retry-After: 23
{
"success": false,
"error": {
"code": "rate_limited",
"message": "Too many requests. Please slow down."
}
}Handling rate limits
- Read the headers
- Check
RateLimit-Remainingas you go, and slow down before it reaches 0. - Wait as told
- On a
429, waitRetry-Afterseconds; without it, back off exponentially (1 s, 2 s, 4 s…). - Ask less often
- Cache what doesn't change, import contacts in bulk rather than one by one, and register a webhook for call and conversation events instead of polling for them.
- Share fairly
- Every key of a workspace shares the same limits, so a busy integration can slow another one down. Separate keys per service still let you revoke one without touching the others.
GET /v1/auth/verifyand the MCP server's messages count against counters of their own, with the workspace's limits; each MCP tool call also counts against the workspace's requests.
class NutalkClient {
constructor(apiKey = process.env.NUTALK_API_KEY) {
this.apiKey = apiKey;
this.remaining = Infinity;
this.resetAt = 0;
}
async request(path, options = {}) {
// Close to the hourly limit: wait for the window to reset.
if (this.remaining < 10 && this.resetAt > Date.now()) {
await sleep(this.resetAt - Date.now());
}
const response = await fetch(`https://www.nutalk.ai/v1${path}`, {
...options,
headers: {
Authorization: `Bearer ${this.apiKey}`,
"Content-Type": "application/json",
...options.headers,
},
});
this.remaining = Number(response.headers.get("RateLimit-Remaining") ?? Infinity);
this.resetAt = Number(response.headers.get("RateLimit-Reset") ?? 0) * 1000;
if (response.status === 429) {
// Retry-After (seconds) covers both the hourly and the per-minute window.
await sleep(Number(response.headers.get("Retry-After") ?? 1) * 1000);
return this.request(path, options);
}
return response.json();
}
}
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const client = new NutalkClient();
console.log(await client.request("/agents"));