Most partner integrations have a multi-step happy path: create the resource, attach a payment method, confirm, then reconcile. Docs and mocks walk that path with immortal credentials and unlimited RPM.
Production does not. The partner returns 429 on step 3 after steps 1–2 succeeded. Your client either backs off cleanly, or it retries the whole flow and creates duplicate resources — or it spins until the partner bans the key for looking like an attack.
Short answer
Point the integration at a service twin and force only the expensive operation to return 429. Assert the retry count, fallback backoff, total deadline, and that earlier successful steps do not run again. This proves the policy without consuming provider quota or storing a production API key in CI.
OpenAI-specific 429 posts cover model quota. This is the other case: a third-party SaaS API rate-limits you mid-workflow, and your fixture file never did.
Why CI never sees it
A typical suite:
- Stub
POST /v1/customers→201 - Stub
POST /v1/payment_methods→201 - Stub
POST /v1/confirm→200
Every stub returns success forever. Nobody injects 429 with a Retry-After between step 2 and 3, then asserts:
- you wait at least
Retry-Afterseconds - you do not re-create the customer
- you resume from the failed step with the same idempotency key
Code review cannot invent that case. The agent that wrote the client read the happy-path quickstart. So did you.
Force the failure on the expensive step
FetchSandbox's ElevenLabs twin provides a concrete mid-workflow case. Its text_to_speech workflow first calls GET /v1/voices, then uses the returned voice ID for POST /v1/text-to-speech/{voice_id}.
Under rate_limited, voice discovery still succeeds and only textToSpeech returns:
{
"status": 429,
"error": {
"code": "rate_limit_exceeded",
"detail": "Rate limit exceeded. Please try again later."
}
}
The scenario intentionally omits Retry-After, so it proves your documented fallback backoff and retry ceiling. Test server-directed waiting in a separate case rather than inventing a header the provider response did not contain.
With the FetchSandbox MCP server in Cursor or Claude Code:
Run elevenlabs text_to_speech under rate_limited.
Assert voice discovery succeeds once, generation returns 429,
the client applies fallback backoff, and it stops at its retry budget.
Give me the run receipt.
The receipt shows the successful discovery call followed by the forced 429. Your application test should assert the exact generation-attempt count and requested delays.
Distinct from quota exhaustion
insufficient_quota / hard billing caps are "stop and alert." Mid-workflow rate_limited is "pause and resume without duplicating side effects." Treating them as the same retry loop is how you get silent double-charges after a traffic spike.
The rule
If your product does more than one authenticated partner call per user action, inject a 429 between those calls once, on purpose. Happy-path green is not proof that the middle of the workflow survives partner pressure.
Questions people ask
How can I test API rate limits without consuming quota?
Use a service twin that returns the provider-shaped 429 on demand. Your app runs its normal client code, but no request reaches the provider and no real quota is consumed.
Should I always honor Retry-After?
Honor a valid Retry-After subject to your own attempt and deadline limits. When the header is absent, use a documented capped backoff with jitter. Test the two branches separately.
Should a 429 restart the whole workflow?
No. Resume from the safe failed operation. Do not repeat earlier successful creates, uploads, or discovery calls unless the provider contract explicitly requires it.
Is a temporary rate limit the same as exhausted quota?
No. A temporary rate limit is usually pause-and-retry. A hard quota or billing limit is stop-and-alert. Some providers use HTTP 429 for both and distinguish them in the error code.
Does FetchSandbox need my provider API key?
No. The twin uses synthetic sandbox credentials and state. Keep the real provider key for final connectivity testing after the negative paths pass locally and in CI.