Calls Per Second (CPS) Limit

Overview

Every Vonage account is subject to a Calls Per Second (CPS) limit. It is a cap on how many new outbound calls can be initiated within any rolling one-second window.

The default limit is 3 CPS. Exceeding it causes the platform to reject the excess calls, typically with a 429 Too Many Requests response (REST API) or a SIP 503 Service Unavailable (SIP Trunking).

This guide explains what CPS is, why it exists, how bursts cause failures even when your total call volume is low, and how to design your system to stay within the limit reliably.

Need a higher CPS limit? Limits can be raised on request. Contact Vonage to discuss your requirements.

Why CPS Exists

CPS is a rate-control mechanism, not a capacity limit. It protects platform stability and fair resource allocation across all accounts. Even a large account with a high concurrent call limit can saturate downstream signaling infrastructure if it initiates a large number of calls in the same second.

How Bursts Cause Failures

The most common mistake is confusing average CPS with instantaneous CPS.

Consider a system that needs to place 30 calls over 10 seconds (an average of 3 CPS). If those 30 calls are all triggered simultaneously (e.g. a batch job fires at midnight), they all arrive in the first second and 27 are rejected.

Without throttling (30 calls, all fired at t=0):
t=0s  ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓  ← 30 attempted, 27 rejected
t=1s  ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
...

With throttling (30 calls, 3 per second):
t=0s  ▓▓▓  ← 3 accepted
t=1s  ▓▓▓  ← 3 accepted
t=2s  ▓▓▓  ← 3 accepted
...
(all 30 succeed)

The limit is evaluated at the platform edge, not averaged across a window you control. Your client-side throttling must enforce the rate before calls reach Vonage.

General Best Practices

These principles apply regardless of whether you use Voice API (VAPI) or SIP Trunking.

  1. Enforce the limit on your side, not Vonage's. Do not rely on retrying rejected calls. A 429/503 at scale generates its own traffic and degrades your system. Gate outbound calls before they leave your infrastructure.

Note: If your infrastructure far exceeds your CPS limit, Vonage may temporarily block your traffic to protect the platform and other customers.

  1. Use a token-bucket or leaky-bucket algorithm. These algorithms are purpose-built for rate limiting. A token-bucket refills at a fixed rate (e.g. 3 tokens/second), and each call consumes one token. If the bucket is empty, the call waits. Most languages have libraries that implement this in a few lines of code.

  2. The CPS limit applies to outbound calls only — but across all endpoint types. Inbound calls are not subject to the CPS limit. However, the outbound limit applies uniformly regardless of which endpoint type is being dialed. All of the following count against your CPS budget:

    • phone — outbound call to a PSTN number
    • sip — call to a SIP URI or SIP trunk
    • websocket — each outbound WebSocket connection established as a call leg counts as one call toward CPS
    • app — call to a Client SDK/WebRTC in-app endpoint

    If your application mixes endpoint types in the same second, for example, placing a phone call while simultaneously opening a websocket leg, both count. Design your throttle to track the combined outbound rate across all endpoint types, not per type.

  3. Add jitter when retrying. If you do retry (e.g. on transient network errors), add random jitter (50–500 ms) to the backoff. Synchronized retries from a pool of workers can recreate the original burst.

  4. Monitor and alert on 429/503 responses. Track rejection rates in your logging pipeline. A sudden spike tells you that upstream traffic is bursting. Investigate the source before requesting a CPS limit increase. To monitor real-time CPS consumption at call-creation time, use the return_cps_on_started parameter in your POST /calls request, or returnCpsOnStarted in an NCCO connect action. See the Voice API Webhook Reference for details.

SIP Trunking: PBX-Side CPS Configuration

When using Vonage SIP Trunking, your PBX is the originator of outbound SIP INVITE messages. The CPS limit must be enforced at the PBX before calls reach the Vonage SIP gateway. Most enterprise PBX platforms have a built-in outbound call throttling or trunk group pacing feature.

Important: These settings limit the rate of new outbound SIP signalling, not active call capacity. Set them to match or stay slightly below your Vonage CPS limit to leave a safety margin for transient traffic spikes.

Note: The following configurations are illustrative. Please contact your vendor for help in creating your own configuration.

Asterisk / FreePBX

In Asterisk, outbound call rate throttling is implemented at the dialplan level using the GROUP_COUNT(${EPOCH}) mechanism, which counts calls initiated within the current second and holds excess calls in a wait loop:

[globals]
calls_per_sec=3

[OUTBOUND]
exten => _X.,1,Set(GROUP()=${EPOCH})
same => n,GotoIf($[${GROUP_COUNT(${EPOCH})}>${calls_per_sec}]?DELAY,${EXTEN},1)
same => n,Dial(PJSIP/vonage-trunk/${EXTEN})

[DELAY]
exten => _X.,1,Wait(0.5)
same => n,Goto(OUTBOUND,${EXTEN},1)

The call-limit option on a PJSIP endpoint controls maximum concurrent channels, not rate. It is useful as a ceiling but not a CPS control.

For more information, see Asterisk res_pjsip Configuration.

3CX

In 3CX, CPS can be managed using the Simultaneous Calls setting under Admin > Voice & Chat > [Trunk] > Options tab, which caps the total number of concurrent calls through the trunk (inbound + outbound combined). Setting this conservatively relative to your Vonage CPS limit provides a practical guard against bursts. Note that this controls concurrent call capacity, not initiation rate.

For more information, see 3CX SIP Trunk Options.

Cisco Unified Communications Manager (CUCM)

In a Cisco environment, CPS throttling is handled by the Cisco Unified Border Element (CUBE), the session border controller that sits between CUCM and the Vonage SIP gateway. On CUBE, use the call-spike threshold and voice service voip rate-limiting directives to enforce CPS. Within CUCM, Locations provide call admission control based on bandwidth, which governs concurrent capacity.

For more information, see Cisco CUBE Configuration Guide.

Avaya Aura / Session Manager

In Avaya environments, CPS enforcement is handled by the Avaya Session Border Controller for Enterprise (SBCE), which supports per-trunk signalling rate policies. Avaya Session Manager manages call volume through Locations and SIP Entity Links, which govern concurrent capacity per link.

For more information, see Avaya SBCE Documentation.

FreeSWITCH

FreeSWITCH enforces a global new-session rate limit via the sessions-per-second parameter in autoload_configs/switch.conf.xml. This caps the rate of all new call legs system-wide:

<!-- autoload_configs/switch.conf.xml -->
<param name="sessions-per-second" value="3"/>
<param name="max-sessions" value="1000"/>

For per-gateway CPS control, use the dialplan limit application to enforce rate limiting against a specific gateway resource.

For more information, see FreeSWITCH SBC Setup/Call Admission Control.

Voice API: Throttling Outbound Calls

When placing outbound calls via the Voice API, your application directly controls the rate of POST /calls requests. The platform will return HTTP 429 if you exceed your CPS limit on the initial POST /calls request. However, subsequent calls initiated via connect actions in the NCCO are processed asynchronously — if these exceed the CPS limit, no 429 is returned to the original request. Instead, a rejected status callback is delivered to your event URL with the detail field set to throttled:

{
  "from": "442079460000",
  "to": "447700900000",
  "detail": "throttled, see knowledge article https://api.support.vonage.com/hc/en-us/articles/207100288",
  "conversation_uuid": "CON-aaaaaaaa-bbbb-cccc-dddd-0123456789ab",
  "status": "rejected",
  "direction": "outbound",
  "timestamp": "2020-01-01T12:00:00.000Z"
}

Note: Make sure your event webhook handler accounts for rejected callbacks with a throttled detail, not just HTTP 429 responses.

Note: A call UUID is only generated after a call resource is created. If a call is rejected because the CPS limit is exceeded, the request is rejected before resource creation and no call UUID is generated.

Implementing a Simple Token-Bucket Throttle

The example below shows how to dispatch a batch of outbound calls while respecting the CPS limit. It uses a straightforward leaky-bucket pattern with asyncio (Python), which is representative of the logic needed in any language.

import asyncio
import httpx
import random
import time

VONAGE_API_URL = "https://api.nexmo.com/v1/calls"
CPS_LIMIT = 3          # Match your account's CPS limit
JITTER_MS  = 100       # Add up to 100 ms of random jitter per call

async def place_call(client: httpx.AsyncClient, call_payload: dict) -> dict:
    response = await client.post(VONAGE_API_URL, json=call_payload)
    response.raise_for_status()
    return response.json()

async def dispatch_calls(call_payloads: list[dict]):
    """
    Dispatch a batch of outbound calls at a controlled rate.
    Fires CPS_LIMIT calls per second, with per-call jitter.
    """
    interval = 1.0 / CPS_LIMIT          # seconds between each call slot
    async with httpx.AsyncClient(headers={"Authorization": "Bearer <JWT>"}) as client:
        tasks = []
        for i, payload in enumerate(call_payloads):
            # Sleep until the next call slot, plus random jitter
            jitter = random.uniform(0, JITTER_MS / 1000)
            await asyncio.sleep((interval if i > 0 else 0) + jitter)
            task = asyncio.create_task(place_call(client, payload))
            tasks.append(task)
            print(f"[{time.strftime('%H:%M:%S')}] Dispatched call {i+1}/{len(call_payloads)}")
        results = await asyncio.gather(*tasks, return_exceptions=True)
        failures = [r for r in results if isinstance(r, Exception)]
        if failures:
            print(f"{len(failures)} call(s) failed: {failures}")
        return results

# Example usage
if __name__ == "__main__":
    calls = [
        {
            "to":   [{"type": "phone", "number": "14155550100"}],
            "from": {"type": "phone", "number": "14155550199"},
            "ncco": [{"action": "talk", "text": "Hello from Vonage."}]
        }
        # ...
        # repeat for each call
    ] * 30  # 30 calls total
    asyncio.run(dispatch_calls(calls))

Key Points

  • interval = 1.0 / CPS_LIMIT ensures exactly one call slot per ~333 ms at 3 CPS. Adjust when your limit is raised.
  • Jitter prevents all workers in a multi-process deployment from firing at the same millisecond boundary.
  • asyncio.gather allows all calls to be in-flight concurrently once dispatched — the throttle only applies to the initiation rate, not the duration.
  • For multi-process or distributed systems, the token bucket must be shared (e.g. via Redis with a Lua script or a dedicated rate-limit sidecar). A per-process bucket will exceed your account-wide limit if you run multiple workers.

Handling 429 Responses

async def place_call_with_retry(client, payload, max_retries=3):
    for attempt in range(max_retries):
        try:
            return await place_call(client, payload)
        except httpx.HTTPStatusError as e:
            if e.response.status_code == 429 and attempt < max_retries - 1:
                backoff = (2 ** attempt) + random.uniform(0, 1)
                print(f"Rate limited. Retrying in {backoff:.2f}s...")
                await asyncio.sleep(backoff)
            else:
                raise

Use exponential backoff with jitter (not a fixed sleep) to avoid synchronized retry storms across workers.

Accounts with Subaccounts: Hierarchical CPS Enforcement

This section is only relevant if you use Vonage subaccounts (sub-API keys). If your integration uses a single main API key, you can skip it. See the Subaccounts API overview for general subaccount management.

How the Two-Layer Model Works

CPS is enforced at two levels simultaneously:

  1. Sub-account limit: Each individual sub-API key has its own CPS cap. Traffic originating from that key cannot exceed it.
  2. Main API key cap: The aggregate CPS across all keys (main + every sub-account) is bounded by the main API key's CPS setting. This is a hard ceiling on total account traffic.

Both limits apply at the same time. A call is rejected if either the sub-account limit or the main-key cap is reached.

Example

Key Individual CPS Limit
Main API key 20 CPS
Sub-API key 1 10 CPS
Sub-API key 2 10 CPS
Sub-API key 3 10 CPS

At any given second, sub-key 1 can fire at most 10 calls, sub-key 2 at most 10 calls, and sub-key 3 at most 10 calls, but the combined total from all three (plus any calls on the main key itself) cannot exceed 20 CPS. The sum of sub-account limits (30) is irrelevant — the main cap always wins.

Main API key cap: 20 CPS
┌─────────────────────────────────────────────┐
│  Sub-key 1  │  Sub-key 2  │  Sub-key 3      │
│  ≤ 10 CPS   │  ≤ 10 CPS   │  ≤ 10 CPS       │
│                                             │
│  Combined total must stay ≤ 20 CPS          │
└─────────────────────────────────────────────┘

What This Means in Practice

Subaccounts share the parent's budget. If you run multiple workloads or customers on separate sub-keys, they compete for the same main-key allowance. A traffic spike on one sub-account can cause rejections on all others, even if each individual sub-account is within its own limit.

Both SIP Trunking and VAPI calls count toward the same combined cap. A mix of SIP INVITE messages and POST /calls REST requests all draw from the same main-key ceiling. If you use both products under the same account hierarchy, model your capacity accordingly.

The main-key limit is the number to watch. When you request a CPS increase, make sure you are increasing the main API key's limit (raising only a sub-account's limit while leaving the main cap unchanged will have no effect if the main cap is already the binding constraint).

Designing for Subaccount CPS

  • Set the main-key cap to match peak aggregate demand, not the sum of sub-account limits. If sub-accounts will rarely all burst at once, a main cap lower than the total of sub-limits is fine, but model the worst-case scenario.
  • Allocate sub-account limits intentionally. Keep sub-account individual limits proportional to the traffic share you expect from each workload. Oversized sub-account limits give a false sense of headroom.
  • Apply per-sub-account throttling in code, not just at the main-key level. Even if the main cap is generous, an unthrottled sub-account can starve others by consuming a disproportionate share.

Requesting a Higher CPS Limit

The default 3 CPS limit is sufficient for most development and low-volume production use cases. For high-volume outbound campaigns, contact centre deployments, or SIP trunks serving large PBX installations, a higher limit is available. Contact Vonage with your use case and expected call volumes. Vonage will assess the request and provision an increased limit on your account, typically reflected within one business day.

Further Reading