Getting Started
Rate limits
What to expect under load, how to detect throttling, and how to back off correctly.
TODO — verify before release
OnCore does not currently publish numeric rate limits for the Core API, and the OpenAPI spec declares no 429 responses outside the WhatsApp onboarding endpoints. The behavior described below is the contract you should build against; the specific numbers must be confirmed with the API team before this page ships.
What is documented today#
Two endpoints return 429 with a documented cooldown. Both come from Meta's WhatsApp platform rather than OnCore itself:
| Code | Endpoint | Cooldown field |
|---|---|---|
WHATSAPP_RESEND_TOO_SOON | Resend the verification call | details.retryAfterSeconds |
WHATSAPP_OTP_REQUEST_LIMIT | Start or resume a signup | details.retryAfterHours |
See WhatsApp onboarding for the full lifecycle.
Build for throttling anyway#
An API without published limits is not an API without limits. Write the client that survives one:
Detect it#
Treat any 429 as throttling, and treat 5xx as potentially transient. Branch on error.code, never on the message:
const res = await fetch(url, { headers });
const body = await res.json();
if (res.status === 429) {
// Cooldown, when the endpoint supplies one.
const seconds =
body.error?.details?.retryAfterSeconds ??
(body.error?.details?.retryAfterHours ?? 0) * 3600;
return { retryAfter: seconds || null };
}Back off exponentially, with jitter#
Retrying on a fixed interval synchronizes every client you run into a thundering herd. Randomize the delay:
async function withRetry<T>(
call: () => Promise<Response>,
{ attempts = 5, baseMs = 500 } = {},
): Promise<Response> {
let lastError: unknown;
for (let attempt = 0; attempt < attempts; attempt += 1) {
try {
const res = await call();
if (res.status !== 429 && res.status < 500) return res;
lastError = new Error(`HTTP ${res.status}`);
} catch (error) {
lastError = error;
}
// Full jitter: sleep a random point in the whole backoff window.
const ceiling = baseMs * 2 ** attempt;
await new Promise((r) => setTimeout(r, Math.random() * ceiling));
}
throw lastError;
}Honor an explicit cooldown#
When the response carries retryAfterSeconds or retryAfterHours, wait at least that long — backoff is a floor, not a substitute.
Never retry a POST blindly#
POST is not idempotent here, so a retry after a timeout can create a duplicate. Reconcile before retrying — see Idempotency.
Reduce the load instead#
The cheapest request is the one you never send.
- Page large, not often.
limitaccepts up to500; one request beats ten. - Cache reference data. Voices, models and transcribers change rarely — read them at startup, not per call.
- Let webhooks push. Do not poll Calls for completion; take the end-of-call report instead. This is the single biggest reduction available to most integrations.
- Filter server-side. Use
searchrather than listing everything and filtering locally.
Next#
- Errors — every status code and error code.
- API conventions — pagination, identifiers, idempotency.