Mobile API Best Practices · Mobile Auth, Security & Flaky Networks
Flaky Networks: Timeouts, Retries & Idempotency
Reliable requests over unreliable links.
On mobile, the request may reach the server while the response is lost. So: set timeouts (connect ~10 s, request per endpoint), retry with exponential backoff + jitter only idempotent requests, send an Idempotency-Key on every non-idempotent mutation (POST payment, create order), respect Retry-After, and pause retries when offline (connectivity callbacks) instead of burning battery. Show clear UI states: sending, queued, failed (tap to retry).
Retry decision table
| Situation | Client behavior | Server support |
|---|---|---|
| Timeout on GET | Retry with backoff | None needed |
| Timeout on POST /orders | Retry with same Idempotency-Key | Idempotency store returns original result |
| 429 / 503 with Retry-After | Wait the given time | Send Retry-After |
| Offline | Queue; resume on connectivity | — |
| 4xx validation error | Don't retry; show error | RFC 9457 problem details |
| Retry storm after outage | Jittered backoff + caps | Load shedding, rate limits |
val client = OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.callTimeout(30, TimeUnit.SECONDS)
.addInterceptor { chain ->
val req = chain.request()
val key = req.header("Idempotency-Key")
?: if (req.method == "POST") UUID.randomUUID().toString() else null
chain.proceed(if (key != null) req.newBuilder().header("Idempotency-Key", key).build() else req)
}
.build()
// Important: generate the key once per user action and reuse it across retries (store it with the pending mutation).
func send<T: Decodable>(_ req: URLRequest, attempts: Int = 4) async throws -> T {
var delay = 0.5
for attempt in 1...attempts {
do { return try await perform(req) }
catch let e as APIError where e.isRetryable && attempt < attempts {
let wait = e.retryAfter ?? Double.random(in: 0...delay) // full jitter
try await Task.sleep(for: .seconds(wait))
delay = min(delay * 2, 8)
}
}
throw APIError.exhausted
}
Common pitfalls
- Generating a new idempotency key on each retry (defeats the purpose).
- Retrying 4xx errors.
- Immediate synchronized retries after an outage.
Key points
- Lost responses are normal — make mutations idempotent
- Backoff + jitter, respect Retry-After
- Pause when offline
Further reading: IETF: Idempotency-Key header draft
Practice questions
Why must the idempotency key be reused across retries of the same action?
Answer
The server uses it to recognize the retry as the same operation and return the original result; a new key looks like a new operation and can create duplicates.
Which errors should a mobile client retry automatically?
Answer
Network failures/timeouts and 408/429/502/503/504 (respecting Retry-After), and only for idempotent requests or those with an idempotency key; not 4xx validation/auth errors.
Open in the app to mark lessons complete, take notes and track your progress.