Skip to main content

Retry strategies

Reqon provides configurable retry strategies for handling transient failures. Choose the right strategy based on your API's behavior.

Retry configuration

retry: {
maxAttempts: 5,
backoff: exponential,
initialDelay: 1000,
maxDelay: 60000
}

Backoff strategies

Exponential backoff

Best for most APIs. Delays double after each attempt:

match response {
{ code: 429 } -> retry {
maxAttempts: 5,
backoff: exponential,
initialDelay: 1000
},
_ -> continue
}

Timeline:

Attempt 1: immediate
Attempt 2: wait 1000ms (1s)
Attempt 3: wait 2000ms (2s)
Attempt 4: wait 4000ms (4s)
Attempt 5: wait 8000ms (8s)

Linear backoff

Delays increase by a fixed amount:

match response {
{ code: 503 } -> retry {
maxAttempts: 5,
backoff: linear,
initialDelay: 2000
},
_ -> continue
}

Timeline:

Attempt 1: immediate
Attempt 2: wait 2000ms (2s)
Attempt 3: wait 4000ms (4s)
Attempt 4: wait 6000ms (6s)
Attempt 5: wait 8000ms (8s)

Constant backoff

Same delay every time:

match response {
{ code: 504 } -> retry {
maxAttempts: 5,
backoff: constant,
initialDelay: 5000
},
_ -> continue
}

Timeline:

Attempt 1: immediate
Attempt 2: wait 5000ms (5s)
Attempt 3: wait 5000ms (5s)
Attempt 4: wait 5000ms (5s)
Attempt 5: wait 5000ms (5s)

Maximum delay

Prevent extremely long waits:

retry: {
maxAttempts: 10,
backoff: exponential,
initialDelay: 1000,
maxDelay: 30000 // Cap at 30 seconds
}

Without cap (exponential):

Attempt 8: wait 128000ms (2+ min)
Attempt 9: wait 256000ms (4+ min)

With maxDelay: 30000:

Attempt 8: wait 30000ms (30s)
Attempt 9: wait 30000ms (30s)

Fixed delay

Override backoff calculation:

match response {
{ code: 429, retryAfter: seconds } -> retry {
maxAttempts: 5,
delay: seconds * 1000 // Use API-provided delay
},
_ -> continue
}

Retry based on error type

Transient errors (should retry)

match response {
{ code: 408 } -> retry, // Request Timeout
{ code: 429 } -> retry, // Too Many Requests
{ code: 500 } -> retry, // Internal Server Error
{ code: 502 } -> retry, // Bad Gateway
{ code: 503 } -> retry, // Service Unavailable
{ code: 504 } -> retry, // Gateway Timeout
_ -> continue
}

Permanent errors (don't retry)

match response {
{ code: 400 } -> abort "Bad request", // Won't improve
{ code: 401 } -> abort "Unauthorized", // Need new creds
{ code: 403 } -> abort "Forbidden", // Permission issue
{ code: 404 } -> skip, // Resource gone
{ code: 422 } -> abort "Invalid data", // Validation error
_ -> continue
}

Conditional retry

match response {
// Retry rate limits with longer wait
{ code: 429 } -> retry {
maxAttempts: 10,
backoff: exponential,
initialDelay: 60000 // Start at 1 minute
},

// Retry server errors with shorter wait
{ code: 500 } -> retry {
maxAttempts: 3,
backoff: exponential,
initialDelay: 1000
},

// Retry timeouts with medium wait
{ code: 504 } -> retry {
maxAttempts: 5,
backoff: linear,
initialDelay: 5000
},

_ -> continue
}

Retry-After header

Respect API's Retry-After header:

match response {
{ code: 429, headers: h } where h["Retry-After"] != null -> retry {
delay: toNumber(h["Retry-After"]) * 1000
},
{ code: 429 } -> retry {
maxAttempts: 5,
backoff: exponential,
initialDelay: 60000
},
_ -> continue
}

Retry with token refresh

action FetchProtectedData {
get "/protected"

match response {
{ code: 401 } -> jump RefreshToken then retry,
{ code: 429 } -> retry { maxAttempts: 5 },
{ code: 500 } -> retry { maxAttempts: 3 },
_ -> continue
}
}

action RefreshToken {
post "/auth/refresh" {
body: { refreshToken: env("REFRESH_TOKEN") }
}
}

Retry at source level

Configure default retry for all requests:

source API {
auth: bearer,
base: "https://api.example.com",
retry: {
maxAttempts: 3,
backoff: exponential,
initialDelay: 1000
}
}

Override per request:

get "/critical-endpoint" {
retry: {
maxAttempts: 10 // More attempts for critical requests
}
}

Choosing the right strategy

ScenarioRecommended Strategy
General API errorsExponential, 3-5 attempts
Rate limitingExponential, long initial delay
TimeoutsLinear, medium delays
Flaky networkConstant, short delays
Critical operationsExponential with high maxAttempts

Best practices

Start small, increase gradually

retry: {
maxAttempts: 5,
backoff: exponential,
initialDelay: 1000, // Start small
maxDelay: 60000 // Cap at reasonable max
}

Be respectful to APIs

// Good: respect rate limits
retry: {
maxAttempts: 5,
backoff: exponential,
initialDelay: 5000
}

// Risky: aggressive retries
retry: {
maxAttempts: 100,
backoff: constant,
initialDelay: 100
}

Log retry attempts

match response {
{ code: 503 } -> {
store {
event: "retry",
code: 503,
timestamp: now()
} -> retryLog
retry { maxAttempts: 3 }
},
_ -> continue
}

Have a fallback

match response {
{ error: _ } -> {
// After max retries, queue for later
queue failed { item: { request: currentRequest } }
skip
},
_ -> continue
}

Troubleshooting

Retries not happening

Ensure match directive triggers retry:

// This triggers retry
match response {
{ error: _ } -> retry,
_ -> continue
}

// This does NOT retry
get "/data" {
retry: { maxAttempts: 3 } // Only triggers on HTTP errors
}

Too many retries

Lower maxAttempts or add maxDelay:

retry: {
maxAttempts: 3,
maxDelay: 30000
}

Retrying wrong errors

Be specific about which errors to retry:

match response {
// Only retry specific codes
{ code: 429 } -> retry,
{ code: 503 } -> retry,
// Don't retry 400, 401, 404, etc.
_ -> continue
}