Azure OpenAI PTU capacity exceeded — throttling on Provisioned Throughput Units (2026) — Fix Guide (2026) | AI Error Hub
Home Providers Azure OpenAI PTU capacity exceeded
Azure OpenAI Capacity · Provisioned Throughput Severity: High HTTP 429

Azure OpenAI PTU capacity exceeded — throttling on reserved throughput

You reserved Provisioned Throughput Units so this would never happen. It happens anyway when the utilization model is misunderstood — this page shows exactly how PTU capacity is measured and how to stop the 429s.

By Sana K. · Cloud AI Reliability Engineer Published 2026-07-26 Verified 2026-07-26

Quick fix (TL;DR)

Resolution: PTU (Provisioned Throughput Units) throttles on utilization percentage, not a fixed RPM or TPM. Every request consumes PTU based on its input+output token shape. When 100% of your PTUs are in-flight, new requests get 429. Fix by (a) sizing PTU using the Azure Capacity Calculator with p95 request shape, (b) enabling spillover to a standard deployment, and (c) shedding load on the client with a token bucket keyed to PTU utilization.

Real error messages you'll see

These are the exact strings returned by the Azure OpenAI service and its SDKs when this error occurs. Copy-paste-searching any of them should land on this page.

Python SDK — PTU 429
openai.RateLimitError: Error code: 429 - {'error': {'code': '429', 'message': 'Requests to the ChatCompletions_Create Operation under Azure OpenAI API version 2024-10-21 have exceeded call rate limit of your current OpenAI S0 pricing tier. Please retry after 3 seconds.'}}
Response headers on PTU 429
HTTP/1.1 429 Too Many Requests
retry-after: 3
retry-after-ms: 2400
x-ratelimit-remaining-requests: 0
x-ratelimit-remaining-tokens: 0
apim-request-id: 8a3d-...
Azure Monitor — PTU utilization metric
AzureOpenAIProvisionedManagedUtilization = 100.0
(threshold: 100%; when sustained, new requests receive 429)

Reference

What consumes PTU vs standard capacity

PTU is measured in units of throughput — each unit represents a slice of GPU capacity. Input tokens and output tokens consume PTU at different rates depending on the model.

Model (deployment SKU=ProvisionedManaged)Approx. tokens per PTU-second (input)Approx. tokens per PTU-second (output)Minimum PTU
gpt-4o (2024-08-06)~50~1350
gpt-4o-mini~250~6525
gpt-4.1~40~1050
o3-mini (reasoning)~30~850
text-embedding-3-large~500 (embeddings only)n/a25

PTU vs standard vs global deployments — throttling model

Deployment typeThrottling based onTypical use caseSpillover option
StandardRegional TPM + RPM quotasDev, low volumeFallback to another region
ProvisionedManaged (PTU)PTU utilization % of your reservationSteady production trafficYes — to standard
GlobalStandardGlobal TPM + RPM, higher ceilingsBest-effort spike absorptionn/a
DataZoneStandardData-residency-bounded TPM/RPMEU-resident workloadsTo standard in same zone
GlobalBatch24-hour SLA, no per-second limitAsync large jobsn/a

Root causes, ranked by frequency

Based on developer reports across Azure OpenAI SDK forums, GitHub issues, and Microsoft Q&A during 2025–2026.

  • 35%
    Undersized PTU reservation — you sized on average request shape but p95 requests (long context, long output, reasoning models) consume 3-5× the PTU per second. Always size on p95, not median.
  • 22%
    Bursty traffic pattern — PTU is provisioned for steady state; a 10× spike over 30 seconds saturates 100% utilization even if your daily average is 40%.
  • 15%
    No spillover configured — when PTU saturates, requests fail instead of overflowing to a standard deployment. Spillover is opt-in and off by default.
  • 10%
    Long-running requests holding PTU — reasoning models (o1, o3) can take 30-60s per request, occupying PTU the entire time. Ten concurrent 60s requests can saturate 50 PTU.
  • 8%
    Retry storms — client retries on 429 without honouring retry-after multiply the effective load and keep PTU pinned at 100%.
  • 6%
    Wrong model on PTU — deploying gpt-4o on 25 PTU (the minimum for mini) is below the model minimum. Azure allocates the requested PTU but the model needs more to function without throttling.
  • 3%
    Shared PTU across environments — dev, staging, and prod all pointing at one 50 PTU deployment. A dev load test can starve production.
  • 1%
    Region-level saturation — very rare, but Azure has capped total PTU in a region during shortages. Your reservation is honoured but new expansions are refused.

Fixes — copy-paste solutions

Fix #1

Size PTU using the Azure Capacity Calculator with p95 request shape

Right-sizing is the fix for 60% of PTU 429s.

Compute your p95 input tokens, p95 output tokens, and target requests-per-second over a real production window (7 days minimum). Feed those into the Azure AI Foundry Capacity Calculator or the token-throughput formula below. Provision at 1.3-1.5× the calculated PTU to leave burst headroom.

size_ptu.py
"""Size PTU from real request telemetry.

Feed this Application Insights or Log Analytics data:
  - p95 input tokens per request
  - p95 output tokens per request
  - target sustained RPS
"""
from dataclasses import dataclass

# Rough tokens-per-PTU-second for common models (2026-01, verify in Azure docs)
MODEL_INPUT_TPS_PER_PTU  = {"gpt-4o": 50,  "gpt-4o-mini": 250, "gpt-4.1": 40, "o3-mini": 30}
MODEL_OUTPUT_TPS_PER_PTU = {"gpt-4o": 13,  "gpt-4o-mini": 65,  "gpt-4.1": 10, "o3-mini": 8}

@dataclass
class TrafficShape:
    model: str
    p95_input_tokens: int
    p95_output_tokens: int
    target_rps: float
    burst_headroom: float = 1.4  # +40% for spikes

def size_ptu(t: TrafficShape) -> int:
    input_tps  = t.p95_input_tokens  * t.target_rps
    output_tps = t.p95_output_tokens * t.target_rps

    ptu_for_input  = input_tps  / MODEL_INPUT_TPS_PER_PTU[t.model]
    ptu_for_output = output_tps / MODEL_OUTPUT_TPS_PER_PTU[t.model]

    # PTU serves whichever side is heavier at a given moment
    base_ptu = max(ptu_for_input, ptu_for_output)
    sized = int(base_ptu * t.burst_headroom)

    # Round up to increments of 25 (Azure's granularity)
    return ((sized + 24) // 25) * 25

# Example: chat app doing 10 RPS with 1500-token prompts and 400-token responses
t = TrafficShape(model="gpt-4o", p95_input_tokens=1500, p95_output_tokens=400, target_rps=10.0)
print(f"Recommended PTU: {size_ptu(t)}")   # -> 100 PTU on gpt-4o
These tokens-per-PTU numbers change with model updates. Always cross-reference the current Azure Capacity Calculator before committing to a reservation — PTU is billed per hour and cannot be downsized instantly.
Fix #2

Enable PTU spillover to a Standard deployment

When PTU saturates, overflow to standard instead of failing.

Azure supports opt-in spillover: requests that would exceed PTU capacity are automatically routed to a companion Standard deployment. You pay standard rates only for the overflowed tokens. Configure via the header x-ms-spillover-deployment-name or set it as a default on the PTU deployment.

spillover_client.py
import os
from openai import AzureOpenAI

client = AzureOpenAI(
    api_key=os.environ["AZURE_OPENAI_API_KEY"],
    azure_endpoint=os.environ["AZURE_OPENAI_ENDPOINT"],
    api_version="2024-10-21",
)

# PTU deployment, with header pointing to a Standard fallback
response = client.chat.completions.create(
    model="gpt4o-ptu",                     # PTU deployment
    messages=[{"role": "user", "content": "Hello"}],
    max_tokens=200,
    extra_headers={
        "x-ms-spillover-deployment-name": "gpt4o-standard",   # fallback
    },
)

# Response headers tell you which deployment served the request
served_by = response.model_extra.get("x-ms-deployment-name") or "unknown"
print(f"Served by: {served_by}")
cli_setup.sh
# Provision a Standard deployment of the SAME model to receive spillover
az cognitiveservices account deployment create \
  --name my-aoai-resource \
  --resource-group my-rg \
  --deployment-name gpt4o-standard \
  --model-name gpt-4o \
  --model-version 2024-08-06 \
  --model-format OpenAI \
  --sku-capacity 100 \
  --sku-name Standard

# Verify PTU deployment carries the spillover default
az cognitiveservices account deployment show \
  --name my-aoai-resource -g my-rg \
  --deployment-name gpt4o-ptu \
  --query "properties.spilloverDeploymentName"
Spillover only works when the fallback deployment hosts the same underlying model + version. A gpt-4o PTU cannot spill to a gpt-4o-mini standard — the response quality would silently degrade.
Fix #3

Client-side load shedding keyed to PTU utilization

Refuse work before it gets throttled.

Poll the Azure Monitor metric AzureOpenAIProvisionedManagedUtilization every 30 seconds and use it as a circuit-breaker signal. When utilization is above 85%, downgrade to a cheaper model, queue non-urgent requests, or return a "system busy" response for low-priority traffic.

ptu_load_shedder.py
import time
import threading
from azure.identity import DefaultAzureCredential
from azure.monitor.query import MetricsQueryClient, MetricAggregationType

RESOURCE_ID = "/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.CognitiveServices/accounts/{acct}"
credential = DefaultAzureCredential()
metrics_client = MetricsQueryClient(credential)

class PTUUtilizationTracker:
    """Polls PTU utilization and exposes a boolean 'saturating' flag."""

    def __init__(self, resource_id: str, poll_interval: int = 30, threshold: float = 85.0):
        self.resource_id = resource_id
        self.poll_interval = poll_interval
        self.threshold = threshold
        self._utilization = 0.0
        self._stop = threading.Event()

    @property
    def saturating(self) -> bool:
        return self._utilization >= self.threshold

    def start(self):
        threading.Thread(target=self._loop, daemon=True).start()

    def stop(self):
        self._stop.set()

    def _loop(self):
        while not self._stop.is_set():
            try:
                response = metrics_client.query_resource(
                    self.resource_id,
                    metric_names=["AzureOpenAIProvisionedManagedUtilization"],
                    granularity="PT1M",
                    aggregations=[MetricAggregationType.MAXIMUM],
                )
                for metric in response.metrics:
                    for ts in metric.timeseries:
                        latest = ts.data[-1].maximum if ts.data else 0
                        self._utilization = float(latest or 0)
            except Exception:
                pass  # Keep last known value on error
            self._stop.wait(self.poll_interval)

# Application code
tracker = PTUUtilizationTracker(RESOURCE_ID)
tracker.start()

def handle_request(is_urgent: bool):
    if tracker.saturating and not is_urgent:
        return {"error": "system_busy_retry_later", "utilization": tracker._utilization}
    # ... make the actual OpenAI call ...
This adds ~200ms latency amortized over 30s polls but converts a hard 429 wall into graceful degradation. Combine with spillover for defense in depth.

Prevention checklist

Ship these seven safeguards once and this error stops appearing in your logs.

  • Size PTU on p95 request shape, not median. Add 40% burst headroom on top.
  • Always provision a companion Standard deployment of the same model + version for spillover.
  • Set alerts on AzureOpenAIProvisionedManagedUtilization at 75% (warning) and 90% (critical) in Azure Monitor.
  • Never share one PTU deployment across dev, staging, and prod — dev load tests will starve prod.
  • Honour the retry-after-ms header exactly. Do not retry sooner and do not retry more than 3 times.
  • For reasoning models (o1, o3), size PTU 2-3× larger than non-reasoning at same RPS — long thinking time holds PTU.
  • Publish a runbook: "PTU 429 spike" → check utilization metric → confirm spillover is on → consider adding PTU (Azure sizes up in ~5 min).

Frequently asked questions

No — it changes the rate limit model. On Standard deployments you hit RPM/TPM ceilings that Azure sets globally per subscription. On PTU you hit 100% utilization of your reservation. PTU gives you predictable capacity and eliminates noisy-neighbour effects, but under-provisioned PTU still throttles.
Azure will scale a PTU deployment up in 3-10 minutes if capacity exists in the region. There is no guarantee — during major model launches, regions can be capacity-locked. Best practice is to keep 30-40% headroom permanently and add PTU in advance of expected traffic events.
It depends on the model. gpt-4o starts at 50 PTU, gpt-4o-mini at 25 PTU, o3-mini at 50 PTU. You cannot go below the minimum. For small workloads that never justify a PTU floor, Standard or GlobalStandard is the correct tier.
You pay PTU at the reserved hourly rate regardless of usage, plus standard per-token rates for the spillover portion only. If spillover happens rarely, the extra cost is small. If spillover is happening constantly, that is a sizing signal — permanent PTU expansion is cheaper long-term.
PTU is region-specific — a reservation in East US does not cover West Europe. For multi-region failover you provision PTU in each region and route on the client side. GlobalStandard and GlobalBatch offer cross-region deployment identifiers but do not offer PTU pricing.

Get the weekly AI-error digest

New fixes, provider status recaps, and one deep tutorial — every Tuesday. 8,400+ engineers.