Payments are experiencing issues due to temporary restrictions in Russia. If your payment does not go through, please submit a support request.Our support team is available 24/7 — we are always here to help with hosting and server issues.We are now accepting requests for dedicated server rental and colocation services in our data center.Reminder: we recommend enabling backups for additional data protection.A new VPS/VDS lineup with NVMe storage and improved performance is now available.Maintenance work on some servers has been completed. All services are operating normally.
Article5 min readViews3

429 Retry-After: Seconds or Date — Checking the API Client's Wait Time

Practical analysis of two Retry-After formats for HTTP 429: parsing seconds and HTTP dates, limiting wait time, and testing six boundary cases in Python 3.12.

Comments 0

Timer, network cable, and amber signal lamp next to the package
In this article

If the API returns 429 and includes Retry-After, the client must recognize two valid formats: an integer number of seconds or an HTTP date. The number specifies a relative wait time, while the date is used to calculate a non-negative difference from the response time. Do not guess incorrect values; instead, apply a predefined fallback policy and log the reason.

What Retry-After Specifically Promises

Status code 429 indicates that the client sent too many requests within a certain interval. The response may include a Retry-After header with a hint on how long to wait; this is described in RFC 6585.

The field syntax is defined by RFC 9110: either an HTTP-date or delay-seconds, a non-negative decimal number of seconds. The base grammar RFC 5234 defines DIGIT as ASCII digits 0–9; the isdecimal() method without isascii() is broader than this rule. Therefore, 120 and Sun, 11 Oct 2026 05:02:00 GMT require different parsing branches.

In a small example, we will verify only the wait time calculation. This is a learning model for a client, not a test of a specific 1C-Bitrix API. Environment: CPython 3.12.14, standard library, UTC; check date: October 14, 2026.

Minimal Function

The function has three inputs: the field value, the response time, and a local wait limit. The limit of 300 seconds is chosen for this learning example; for a production client, it is determined separately.

from datetime import datetime from email import utils def retry_after_seconds(value: str, response_date: datetime, max_wait: int = 300) -> int: value = value.strip() if value.isascii() and value.isdecimal(): delay = int(value) else: retry_at = utils.parsedate_to_datetime(value) if retry_at.tzinfo is None: raise ValueError("Retry-After date must include a timezone") delay = max(0, int((retry_at - response_date).total_seconds())) return min(delay, max_wait)

The parsedate_to_datetime() function is provided by the standard Python library. On successful parsing, it returns a datetime object; for invalid date values, it raises a ValueError. Checking tzinfo prevents silently accepting a date without a time zone. Syntax details are available in the Python documentation.

Why response_date is needed

An absolute date must be converted into a duration. If the response contains a Date field, it is convenient to calculate from it: both values are generated server-side, so the possible discrepancy between client and server clocks has less impact on the calculation. This is an engineering measure, not a separate Retry-After rule. Without an explicit Date, the local time of receiving the response is used, taking into account the possible clock discrepancy between the client and the server.

A past HTTP date results in a zero delay, not a negative one. However, zero does not mean "retry infinitely": the next step is still limited by the total attempt budget.

Reproducible check

We record the response time and six cases: both forms defined by the standard, a past date, excessive delay, an invalid string, and decimal Unicode digits outside ASCII.

from datetime import datetime, timezone REFERENCE = datetime(2026, 10, 11, 5, 0, tzinfo=timezone.utc) CASES = [ ("120", 120), ("Sun, 11 Oct 2026 05:02:00 GMT", 120), ("Sun, 11 Oct 2026 04:59:00 GMT", 0), ("86400", 300), ] for raw, expected in CASES: actual = retry_after_seconds(raw, REFERENCE) assert actual == expected, (raw, actual, expected) print(f"{raw!r} -> {actual} s") try: retry_after_seconds("two minutes", REFERENCE) except ValueError as exc: print(f"'two minutes' -> {type(exc).__name__}") else: raise AssertionError("invalid value was accepted") unicode_digits = chr(0x0661) + chr(0x0662) + chr(0x0660) try: retry_after_seconds(unicode_digits, REFERENCE) except ValueError as exc: print(f"'U+0661 U+0662 U+0660' -> {type(exc).__name__}") else: raise AssertionError("Unicode digits outside ASCII were accepted")

The run completed with code 0. Observed output:

'120' -> 120 s 'Sun, 11 Oct 2026 05:02:00 GMT' -> 120 s 'Sun, 11 Oct 2026 04:59:00 GMT' -> 0 s '86400' -> 300 s 'two minutes' -> ValueError 'U+0661 U+0662 U+0660' -> ValueError

Input

Expectation

Result

What it confirms

120

120 s

120 s

Relative delay

HTTP date +120 s

120 s

120 s

Absolute date

HTTP date −60 s

0 s

0 s

No negative delay

86400 with max_wait=300

300 s

300 s

Local limit

two minutes

Error

ValueError

Invalid value rejected

U+0661 U+0662 U+0660

Error

ValueError

Unicode digits outside ASCII rejected

What to do with an empty or incorrect header

  • Do not substitute zero: an immediate retry may extend the limit.

  • Do not convert arbitrary strings to large numbers: parse errors are handled separately.

  • Apply the documented backup delay with jitter and a total attempt limit.

  • Preserve status, Retry-After, and attempt number without tokens or personal data.

  • Stop the retry if the operation is not protected against duplicates.

There is a catch here: a correct delay does not make a POST request safe. After waiting, the connection may still break after the server has already processed the request. To create an order, an idempotent key or another deduplication mechanism is required.

A separate, verifiable example is analyzed in the article "A repeated webhook must not create a second order". Retry-After governs the timing of the next attempt, while idempotency governs its consequences.

Solution Boundaries

  • The example does not send real requests and does not measure the limits of a specific service.

  • Retry-After may be absent: in that case, the client's pre-defined policy applies.

  • A maximum of 300 seconds was chosen only for testing; the protocol does not define such a ceiling.

  • Multiple workers require a shared limiter or coordination, otherwise they will wake up simultaneously.

  • The pause must be interruptible upon stopping the job.

Client Acceptance Criteria

A client is ready for 429 if it handles numbers and HTTP dates identically, does not allow negative delays, rejects garbage, limits wait time and retry counts, and protects repeatable operations against duplicates. It is premature to rely on a single successful retry: error branches and boundary values must also be verified.

Discussion 0

Share your experience and ask questions. Comments without links appear after editorial review.

No comments yet. Start the discussion.