Platform

Rate limits and concurrency

Two things are limited: how fast a workspace may send requests, and how many calls it may be on at once. Both answer 429 when reached, and both are safe to retry.

Rate limits

WhatLimitCounted per
Requests of any kind300 a minuteWorkspace, across all its keys
Calls started20 a minuteWorkspace, across all its keys
Requests with bad credentials30 a minuteNetwork address

Past a limit, requests are refused until the minute is up:

429 Too Many Requests
{
  "error": "Too many requests. Slow down and retry.",
  "code": "RATE_LIMITED",
  "status": 429,
  "retryAfterSeconds": 23
}

The Retry-After header carries the same number of seconds. The limits are there to stop a runaway loop, not to meter normal use.

Calls at once

A workspace can be on a set number of calls at the same time. The number comes from its plan and is returned as maxConcurrentCalls by Check a key. Starting one call too many answers:

429 Too Many Requests
{
  "error": "This workspace can run 5 calls at once and 5 are active. End one or retry shortly.",
  "code": "WORKSPACE_CONCURRENCY_LIMIT_REACHED",
  "status": 429,
  "activeCalls": 5,
  "maxConcurrent": 5
}

A slot is freed when a call ends. Much more rarely, the platform as a whole is at capacity and answers CONCURRENCY_LIMIT_REACHED; retry shortly.

Handling a 429

Wait, then try again, a few times at most. For a caller who is waiting, tell them the line is busy instead of retrying forever.

Retry with a pause
async function startCall(body, attempt = 0) {
  const response = await fetch('https://goblins-call-api.onrender.com/v1/calls', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.CRACKLE_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(body),
  });

  if (response.status === 429 && attempt < 3) {
    // Rate limits say how long to wait; a busy line is worth a short pause and another try.
    const seconds = Number(response.headers.get('retry-after')) || 2 ** attempt;
    await new Promise((resolve) => setTimeout(resolve, seconds * 1000));
    return startCall(body, attempt + 1);
  }
  if (!response.ok) throw new Error((await response.json()).error);
  return response.json();
}

Other limits

WhatLimit
Request body1 MB
Agent instructions25,000 characters
Text for Speak into a call2,000 characters
Webhooks attached to one call5
Calls per page of List calls100
API keys per workspace25
Time a tool’s endpoint has to answer30 seconds