#Rate Limiting

7 min read

The AiDial API enforces application-level and infrastructure-level rate limits to protect service availability. When a request is rejected by a rate limiter, the API returns a 429 Too Many Requests response.

#Detecting Rate Limits

A 429 HTTP status code indicates you have been rate-limited. Before parsing the response body, check the Content-Type header and retry signals:

  • JSON body (application/json): Parse the response for error details and retry timing
  • Non-JSON body (text/plain or text/html): Infrastructure-level rate limit; do not attempt to parse as JSON
  • Retry timing: Prefer the Retry-After response header when present; otherwise use a documented retry field in the JSON body, such as retry_after_seconds

#Current Application and Edge Limits

SurfaceCurrent limitRetry signal
Edge proxy main API routerTraefik average 1,200 requests per 1-minute period with a burst of 300No JSON contract guaranteed
Partner API key and webhook operations5 requests per operation per 60 seconds. API-key create/rotate scope by authenticated identity, target tenant, and data environment; webhook create/rotate-secret/test scope by authenticated identity and target tenantRetry-After header
Knowledge base build and preview12 build/rebuild requests per authenticated identity and tenant per 60 minutes; 30 preview queries per authenticated identity and tenant per 300 secondsRetry-After header
CSV export3 requests per user/tenant pair, and 12 requests per tenant, per 600 secondsretry_after_seconds body
Transcript downloads10 requests per user/tenant pair, and 60 requests per tenant, per 300 seconds across TXT and PDF downloadsretry_after_seconds body
SCIM security endpoints30 configuration updates per authenticated identity, tenant, data environment, and operation per 60 seconds; 120 provisioning events per scoped API-key identity, tenant, and data environment per 60 secondsRetry-After header
SSO security endpoints20 configuration/role-mapping mutations per authenticated identity, tenant, data environment, and operation per 60 seconds; 10 break-glass operations per authenticated identity, tenant, data environment, and operation per 60 secondsRetry-After header
Communication email routingAdd email: 10 per user/tenant per 24 hours; resend verification: 3 per user/email/tenant per 1 hour; public token verification: 10 per source IP per 60 secondsRetry-After header and retry_after_seconds body
Account export jobs3 create requests per user/tenant/data environment, and 10 per tenant/data environment, per 24 hoursRetry-After header
Report schedule sample sends5 per actor/tenant per 600 seconds, and 3 per tenant/frequency/recipient-set per 900 secondsRetry-After header and details.retry_after_seconds body
Business-hours project options20 requests per authenticated actor per 60 secondsfield_errors.retry_after_seconds body
Portal MFA lifecycle mutation5 requests per client, user, and action per 60 secondsRetry-After header and retry_after_seconds body
Compliance PII unlock requests10 unlock requests per actor, tenant, data environment, and call per 15 minutesNo retry timing
Support portal surfacesRoute-local per-actor/operation limits over 60 seconds: ticket search 30, badge/open-count 60, ticket comment read 60, related-call validation 60, related-call attach/detach 20, recipient add/remove 20, attachment readiness/list/scan-status 60, attachment finalise/download 30, attachment session-start/delete 12No retry timing
Public demo dial-meCurrent configured limits are 5 per phone per hour, 10 per phone per day, and 10 per resolved public source IP per day; active call and runtime capacity blocks also return 429No retry timing
Authenticated DialMe dispatchNo request-count limit in the API. POST /v1/dialme/dispatch returns 429 only when the AiDial runtime reports a capacity blockNo retry timing

#Response Formats

For public demo dial-me IP limiting, the source IP is resolved through the trusted request-provenance helper before counters are updated. Spoofed forwarded headers from untrusted direct peers are ignored, while malformed trusted proxy chains or chains that resolve only to internal/trusted infrastructure are rejected with 400 before dispatch.

#General HTTPException-Based Routes

Some routes return the general error shape from the global FastAPI exception handler:

Text
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
JSON
{
  "error": "Rate limit exceeded",
  "status_code": 429
}

Public demo dial-me, authenticated DialMe dispatch, and Compliance PII unlock throttling use this plain-string form; the error value carries the route's message (for example, Too many PII unlock requests. Please retry shortly. or DialMe call capacity reached). These responses do not guarantee retry timing.

When an HTTPException carries a structured detail object, the global handler wraps that object in error. The Support portal surfaces use this form, raising a 429 with a rate_limit_exceeded code and an operation-specific message and no retry timing:

JSON
{
  "error": {
    "code": "rate_limit_exceeded",
    "message": "Too many requests."
  },
  "status_code": 429
}

#Partner Endpoints

Partner endpoints using the shared rate limiter return the partner error envelope and include a Retry-After response header:

Text
HTTP/1.1 429 Too Many Requests
Retry-After: 3
Content-Type: application/json
JSON
{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests. Please retry shortly.",
    "message_key": "rate_limited",
    "status": 429,
    "request_id": "9b603a24-4d95-4d0b-a865-20c0f8a2c5de"
  }
}

Knowledge base build/rebuild and preview rate limits use this same partner envelope and message text with code set to rate_limited.

SCIM and SSO security rate limits use the same partner envelope with code set to rate_limited; the SCIM message text is SCIM-specific.

#Billing Endpoints

Billing 429 responses use the billing domain error shape. When the billing provider supplies retry timing, AiDial includes a Retry-After response header with the number of seconds to wait:

Text
HTTP/1.1 429 Too Many Requests
Retry-After: 60
Content-Type: application/json
JSON
{
  "code": "billing_provider_rate_limited",
  "message": "Billing provider rate limit exceeded."
}

#Export and Transcript Download Endpoints

POST /v1/calls/export, GET /v1/calls/{call_id}/transcript-download, and GET /v1/calls/{call_id}/transcript/pdf return export/download error bodies. Their 429 responses include retry_after_seconds in the response body:

JSON
{
  "error": "rate_limited",
  "code": "EXPORT_THROTTLED",
  "message": "Export throttled. Please wait and try again.",
  "messageKey": "calls.export.messages.throttled",
  "retry_after_seconds": 60
}

Transcript TXT and PDF downloads share the same download limiter and use DOWNLOAD_THROTTLED:

JSON
{
  "error": "rate_limited",
  "code": "DOWNLOAD_THROTTLED",
  "message": "Transcript download throttled. Please wait and try again.",
  "messageKey": "calls.transcriptDownload.messages.throttled",
  "retry_after_seconds": 9
}

Concurrent CSV exports for the same user and tenant return 409 Conflict with EXPORT_IN_PROGRESS, not 429.

#Portal Domain Endpoints

Several portal-facing domain endpoints return a flat domain error shape.

Communication email routing uses code: "rate_limited" and includes both Retry-After and retry_after_seconds:

JSON
{
  "code": "rate_limited",
  "message": "Request rate limit exceeded.",
  "retry_after_seconds": 60
}

Account export job creation uses code: "export_rate_limited" and includes Retry-After:

JSON
{
  "code": "export_rate_limited",
  "message": "Account export request limit reached for this user."
}

Report schedule sample sends include retry timing under details and set Retry-After:

JSON
{
  "code": "rate_limited",
  "message": "Too many requests. Please try again later.",
  "details": {
    "reason": "actor",
    "retry_after_seconds": 600
  }
}

Business-hours project options use the domain error shape and put retry timing in field_errors:

JSON
{
  "code": "business_hours_options_rate_limited",
  "message": "Request rate limit exceeded.",
  "field_errors": {
    "retry_after_seconds": "60"
  }
}

#Portal MFA Lifecycle

PUT /v1/portal-mfa-lifecycle applies a route-local limiter per client, user, and action. Its 429 response uses the domain error shape and includes both Retry-After and retry_after_seconds:

Text
HTTP/1.1 429 Too Many Requests
Retry-After: 60
Content-Type: application/json
JSON
{
  "code": "rate_limited",
  "message": "Too many requests. Please wait and try again.",
  "retry_after_seconds": 60
}

#Infrastructure-Level Rate Limiting

Infrastructure-level rate limiting is enforced by Traefik for the main API router with an average of 1,200 requests per minute and a burst of 300. It may return a 429 response with a non-JSON body (plain text or HTML). Your integration must handle this gracefully:

Text
HTTP/1.1 429 Too Many Requests
Content-Type: text/plain

Rate limit exceeded

The dedicated /v1/sms/telnyx/inbound edge router applies the same API rate-limit middleware plus an additional shared platform rate limiter, so carrier webhook traffic can be throttled at the edge before it reaches the application.

Use exponential backoff with jitter:

  1. Check Content-Type before parsing the 429 body
  2. If Retry-After header is present: Wait the specified number of seconds
  3. If JSON with documented retry timing: Use retry_after_seconds, details.retry_after_seconds, or field_errors.retry_after_seconds where documented for that endpoint
  4. If non-JSON or no retry timing: Wait a minimum of 60 seconds
  5. On subsequent retries: Double the wait time (60s, 120s, 240s) up to a maximum of 10 minutes
  6. Add jitter: Add a random delay of 0-5 seconds to avoid thundering herd effects