Home/Blog/Reliable APIs
Resilient API Architecture

Building Reliable APIs For High-Variance Networks

On a stable office connection, a timeout looks like a server problem. On mobile, satellite, rural broadband, crowded Wi-Fi, or a moving vehicle, the request may have reached the server, the response may have been lost, bandwidth may change mid-transfer, and reconnecting may use a different path. The API contract must make uncertainty safe.

Design for variance, not average latency

Measure the distribution: DNS, connection, TLS, request upload, server processing, first byte, download, packet loss, reconnects, and radio transitions. Segment by country, carrier/ASN, network type, app version, device, endpoint, payload size, and attempt. p50 hides the users who repeatedly cross the timeout boundary.

High round-trip timeEvery handshake and sequential dependency is expensive. Reuse connections, remove chatty calls, batch carefully, and parallelize only within capacity.
Loss and route changeRequests stall, connections migrate or reset, and the client cannot infer whether a write committed.
Variable bandwidthA small response succeeds while a media upload or unbounded list fails halfway through.
Intermittent availabilityApps move between online, captive, degraded, background, and offline states; queued intent outlives a process.

Give the request one end-to-end deadline

A client operation needs a deadline, not independent 30-second timeouts at every hop. Reserve time for DNS/connect/TLS, the first attempt, optional retry, response parsing, and UI feedback. Propagate remaining budget downstream so abandoned work stops. Separate connect timeout from request/idle timeout, and cancel server work when the protocol and execution model permit it.

DNS
Connect
TLS
Try 1
Jitter
Try 2
Budget
Parse
UI

Retry only when method, failure, and budget allow it

HTTP defines safe and idempotent method semantics, but application side effects still need care. Retry GET/HEAD when transient and within budget. PUT/DELETE can be idempotent by contract yet still trigger external effects if implemented poorly. Do not automatically retry POST/PATCH unless the API supplies an idempotency contract. Never retry validation, authentication, authorization, conflict, or permanent not-found errors as if they were packet loss.

Capped exponential backoff with full jitter
T+0Attempt 1Send with request ID, deadline, and idempotency key when required.
transient failure → random 0..base
T+Δ1Attempt 2Reuse the same logical operation ID; stop if budget or policy no longer permits.
random 0..min(cap, base × 2²)
T+Δ2Final attemptReturn a recoverable state, queue intent, or surface failure. Do not loop indefinitely.

Put retries at one layer

If the mobile SDK retries three times, the gateway retries three times, and the service retries its dependency three times, one action can amplify to 27 downstream calls. Choose the layer with the operation semantics and remaining budget. Use a retry quota or token bucket, cap attempts and delay, apply full jitter, respect Retry-After, and disable retries when failure is sustained. Hedged requests should be limited to safe reads and charged to a separate budget.

OutcomeClient interpretationRetry policyServer contractFailure to avoid
Connection lost before responseWrite outcome may be unknown.Retry only if safe/idempotent or keyed.Stable operation status retrievable by key or ID.Create the purchase twice.
408 / timeoutRequest did not complete in the available context.Bounded retry when deadline remains.Stop abandoned work and preserve idempotency.Restart a long operation from zero.
429 / quotaCaller is exceeding a policy.Honor retry information; shape future traffic.Expose machine-readable policy when supported.All clients retry at the same second.
502/503/504Transient upstream, overload, or gateway failure is possible.Small capped retry with jitter; consider stale/read fallback.Load shed early and return Retry-After when meaningful.Retry storm during overload.
409 conflictCurrent resource state rejects the change.Fetch/reconcile; do not blind retry.Return problem details and current revision.Overwrite concurrent work.
4xx validation/authRequest or authority must change.No transport retry.Stable type, field errors, remediation and request ID.Spend battery repeating permanent failure.

Idempotency is a server-side state machine

For a side-effecting operation, the client creates a high-entropy key scoped to principal and route. The server stores key, request fingerprint, state, operation/resource ID, status, response or result reference, timestamps, and expiry. The same key plus same fingerprint returns the original outcome; the same key plus different payload is rejected. Concurrent duplicates wait or receive an explicit “in progress” response. Persist the record atomically with the business effect.

Acknowledge long work asynchronously

If processing exceeds an interactive deadline, accept the command once and return 202 Accepted with an operation URL. The operation exposes pending/running/succeeded/failed/cancelled, progress where meaningful, timestamps, result link, retry guidance, and a stable problem detail. Poll with backoff and ETag, or deliver a signed event. Do not keep a mobile request open for a multi-minute export.

Pagination begins with a stable order

Every list endpoint should be paginated from launch. Cursor tokens should bind the query, filters, sort, snapshot or boundary, direction, and last stable ordering tuple. Prefer immutable unique tie-breakers such as (updated_at, id). Tokens are opaque and integrity-protected. Offset pagination is acceptable for small static admin datasets, but unstable collections cause duplicates and skips under insertion/deletion.

Let clients ask for less

Use bounded page sizes, field masks or sparse fieldsets, summary resources, filters, delta sync, updated_since with stable semantics, and separate media from metadata. Avoid one endpoint that returns a full account graph. A low-bandwidth client should be able to fetch the next actionable screen, not the entire domain model.

Compression is negotiated, measured, and bounded

Support Accept-Encoding and set Vary: Accept-Encoding. Compress text, JSON, protobuf, SVG, and other compressible content above a measured threshold. Avoid recompressing JPEG/AVIF/video and small bodies. Benchmark Brotli, gzip, or Zstandard by size, server CPU, device CPU, battery, and latency. Streaming compression should not buffer the entire response.

Cache and revalidate instead of redownloading

Use explicit Cache-Control, validators such as ETag, If-None-Match, and Last-Modified where semantics permit. Separate user-specific and public content, define Vary correctly, and expose freshness. A degraded UI may show stale data only when product rules allow it, with an as_of timestamp and restricted actions. Cache-Status helps debug cache behavior.

Make large transfer resumable

Upload in chunks with a stable upload ID, offset or part numbers, checksums, expiry, content metadata, and an explicit finalize step. Downloads can use range requests and validators. Retry only missing parts. The tus protocol is one established resumable-upload option. Never mark a file complete before verifying length and digest; preserve the logical attachment ID across reconnects.

Graceful degradation is a product contract

Serve lessSmaller page, summary fields, no thumbnails, reduced precision, lower media quality, deferred enrichment, or cached reference data.
Queue intentSave local command with operation ID, idempotency key, dependency version, expiry, user-visible state, and conflict policy.
Disable risky actionsWhen authorization, price, inventory, balance, or safety state is too stale, allow viewing but block irreversible writes.
Prefer core trafficPrioritize authentication, safety, payment confirmation, sync metadata, and text before analytics, images, and prefetch.
Load shed explicitlyReject optional work early, preserve capacity for critical classes, and publish a retryable problem response.
Show truthful stateOffline, syncing, pending, stale, failed, conflicted, and confirmed are distinct states, not one spinner.

Use a stable machine-readable error contract

RFC 9457 problem details provide type, title, status, detail, and instance. Add stable error codes, field violations, retryability, request/correlation ID, and safe remediation. Do not leak stack traces or make clients parse localized prose. Preserve the specific upstream cause when it is safe and actionable.

Transport improvements do not replace API semantics

HTTP/3 over QUIC can improve behavior under loss and connection changes, while HTTP prioritization can signal urgency. Connection reuse, TLS session resumption, CDN/edge placement, and fewer sequential requests also help. None of these prevents duplicate side effects, unbounded lists, ambiguous errors, or stale writes. Deploy HTTP/3 with fallback and measure by real networks.

Observe attempts, not only requests

Trace one logical operation across all attempts using an operation ID plus per-attempt request IDs. Record DNS/connect/TLS/server/download timing, bytes, compression, cache result, network/carrier, timeout phase, retry reason/delay, idempotency result, page token age, stale mode, queue delay, and final user outcome. Measure retry amplification, duplicate suppression, completion by network class, battery/data cost, and p95/p99.

What I would build

A shared client SDK with deadline propagation, network-aware timeouts, retry classification, capped full-jitter backoff, retry quota, idempotency keys, operation polling, cursor helpers, conditional caching, resumable uploads, local outbox, conflict states, and telemetry. The server platform would provide idempotency storage, problem details, rate/load-shed signals, pagination contracts, field masks, async operations, ETags, request cancellation, and attempt-aware traces.

The principle

A reliable API does not pretend the network is reliable. It makes every uncertain outcome recoverable, every retry bounded, every write deduplicated, every transfer resumable, and every degraded state visible enough for the client and user to make the next correct decision.

Related reading

Article about reliable APIs for high-variance networks, retries, exponential backoff, jitter, idempotency, compression, pagination, caching, resumable transfer, offline queues, and graceful degradation.