Skip to content
CyberTemp
All posts
Handling Rate Limits and Retries in CyberTemp API Scripts

Handling Rate Limits and Retries in CyberTemp API Scripts

CyberTemp6 min read

If your signup-flow script polls GET /getMail in a tight loop, you will eventually get a 429. Not because CyberTemp's API is fragile, but because a tight polling loop is the wrong pattern for waiting on mail that hasn't arrived yet. Here's what the rate limit actually looks like, why the naive approach trips it, and the backoff pattern that keeps a script running cleanly at any plan tier.

What a 429 actually means here

Every API key has a request ceiling tied to its plan tier. Cross it and CyberTemp's API responds with a standard shape:

429 {
  "error": "Too many requests",
  "retryAfter": 4
}

retryAfter is the number of seconds the API wants you to wait before the next call. It's not a suggestion buried in documentation — it's a field on the response body, meant to be read programmatically. If you're using the Go SDK, this shows up as a typed RateLimitError instead of a raw status code, which makes the retry logic below a one-line errors.As check rather than a manual status comparison.

The limit is per API key, scoped to your plan. FREE keys hit it fastest, ECO and CORE raise the ceiling, and ELITE is built for exactly this kind of high-volume polling. GET /getPlan returns your current tier, limit, and usage, so a script can check its own headroom instead of guessing.


The polling loop that trips it

This is the pattern most people write first, because it's the most obvious way to "wait for an email":

import httpx

HEADERS = {"X-API-Key": "tk_..."} BASE = "https://api.cybertemp.xyz"

def wait_for_code(email): while True: r = httpx.get(f"{BASE}/getMail", params={"email": email}, headers=HEADERS) messages = r.json() if messages: return messages[0]

No delay between requests, no handling for a non-200 response. Against a single inbox during manual testing, this might run for a while before anyone notices. Run it across twenty parallel signups in a CI job — each one rightly using its own throwaway inbox instead of sharing state between test runs — and every one of those loops is firing requests as fast as the event loop allows. You'll burn through the rate limit in seconds, and every call after that returns a 429 the loop doesn't know how to interpret — so it just keeps looping, treating the error body as if it were an empty inbox.

The fix isn't a bigger plan. It's a script that respects the signal the API is already sending.


A backoff pattern that respects retryAfter

The corrected version does two things the first one didn't: it waits between polls even on success, and it reads retryAfter when the API tells it to slow down.

import httpx, time

HEADERS = {"X-API-Key": "tk_..."} BASE = "https://api.cybertemp.xyz"

def wait_for_code(email, timeout=60): deadline = time.time() + timeout delay = 1.0 while time.time() < deadline: r = httpx.get(f"{BASE}/getMail", params={"email": email}, headers=HEADERS) if r.status_code == 429: time.sleep(r.json().get("retryAfter", delay)) continue messages = r.json() if messages: return messages[0] time.sleep(delay) delay = min(delay * 1.6, 8.0) raise TimeoutError(f"no message for {email} within {timeout}s")

The baseline poll interval starts at one second and backs off toward eight as the wait drags on — most OTPs and signup confirmations land in the first few seconds, so the loop stays responsive early and gets patient later. If a 429 shows up anyway, the loop sleeps exactly as long as the API asked and tries again, instead of guessing at a delay. This is the same shape the Go SDK's WaitForMessage and WaitForOTP use internally — polling with backoff, not a busy loop.


The other wall: 403 UPGRADE_REQUIRED

If you're on the Go SDK and call WaitForOTP on a FREE key, you won't see a 429 first. You'll see a 403 with code: "UPGRADE_REQUIRED", surfaced as an UpgradeRequiredError. WaitForOTP's regex-based code extraction is a Pro-tier-and-up feature — FREE and ECO keys can still call GET /getMail and WaitForMessage directly, they just don't get the built-in OTP parsing.

A script that assumes every key can call every method will crash confusingly on this the first time it runs against a lower tier. Catch it explicitly:

  • On UpgradeRequiredError, fall back to pulling the raw message body from WaitForMessage and extracting the code with your own regex.

  • Or treat the error as a signal to move that key to a higher tier if OTP extraction is a core part of the workflow, not an occasional one.


Sizing concurrency to your tier

The backoff pattern above fixes one script polling one inbox. The next failure mode shows up when you run many of them at once — a CI matrix, a batch of signup tests, a bot processing a queue. Twenty scripts each backing off individually still adds up to twenty times the request rate at any given moment.

Cap concurrency to something your tier can actually sustain rather than launching every job at once and letting the rate limiter sort it out. A semaphore around the polling calls — even a simple one limiting active pollers to a fixed number — keeps the aggregate request rate under the ceiling instead of testing it. GET /getPlan tells you what that ceiling is for the key you're using, so the cap can be read at runtime instead of hardcoded and forgotten. If your queue is consistently deeper than what your current tier sustains without backoff kicking in constantly, that's the actual signal to move up a tier — not a random 429 in the logs three weeks later.


Bottom line

A 429 isn't the API being difficult — it's the API telling a script exactly how long to wait, in a field built for that purpose. Read retryAfter, back off on every poll instead of just after errors, and size concurrency to what GET /getPlan says your key can sustain. That's the whole pattern, and it's the same one the SDKs use under the hood.

If you're scripting against signups at any real volume — CI pipelines, multi-account workflows, scrape-then-verify flows — the request ceiling is usually the first thing that needs headroom before anything else does. The plan tiers scale that ceiling from FREE up through ELITE, and the OTP-extraction helpers in the Go and Rust SDKs get meaningfully more useful once a key isn't spending its budget on retries. For one-off testing against a single inbox, cybertemp.xyz still works fine in the browser — see the rundown of what the service does end to end — without any of this.

Share