Retry-After comes in two shapes and providers use both. ParseRetryAfter
handles each, and never hands you a value that would make backoff nonsensical.
Reference#
func ParseRetryAfter(v string) time.Duration
Returns
| Header value | Returns |
|---|---|
60 | 60 seconds |
1.5 | 1.5 seconds — fractional values are accepted |
Wed, 04 Aug 2026 21:00:00 GMT | The remaining duration until that time |
(a date in the past) | 0 |
garbage | 0 |
(empty) | 0 |
-5 | 0 |
Caveats
- It never returns a negative duration. A date already in the past yields zero, so backoff falls back to its computed delay rather than doing something nonsensical.
- Zero means "no usable hint", not "retry immediately".
- Leading and trailing whitespace is trimmed.
- The value skyl honours is separately bounded by
WithRetryAfterCap, defaulting to 5 minutes.
Usage#
In an adapter
e := skyl.NewError(p.Name(), res.StatusCode, skyl.ErrRateLimit, msg, raw)
e.RetryAfter = skyl.ParseRetryAfter(res.Header.Get("Retry-After"))
return nil, ee := skyl.NewError(p.Name(), res.StatusCode, skyl.ErrRateLimit, msg, raw)
e.RetryAfter = skyl.ParseRetryAfter(res.Header.Get("Retry-After"))
return nil, eReading the hint as a caller
var e *skyl.Error
if errors.As(err, &e) && e.RetryAfter > 0 {
log.Printf("%s asked for %s", e.Provider, e.RetryAfter)
}var e *skyl.Error
if errors.As(err, &e) && e.RetryAfter > 0 {
log.Printf("%s asked for %s", e.Provider, e.RetryAfter)
}Propagating it to your own callers
if e != nil && e.RetryAfter > 0 {
// So your callers back off in step with the real window rather than guessing.
w.Header().Set("Retry-After", strconv.Itoa(int(e.RetryAfter.Seconds())))
}if e != nil && e.RetryAfter > 0 {
// So your callers back off in step with the real window rather than guessing.
w.Header().Set("Retry-After", strconv.Itoa(int(e.RetryAfter.Seconds())))
}Troubleshooting#
RetryAfter is zero on a 429
The provider did not send the header, or sent something unparseable. skyl falls back to its computed backoff, which is the correct behaviour — a zero is not "retry now".
A retry waited five minutes
That is WithRetryAfterCap's default being reached: the provider asked for at
least that long. Lower the cap if your latency budget cannot absorb it.