- A single async
HTTPClientAPI (send(_:) -> HTTPResponse) - Interceptors for auth, headers, and retry decisions
- Configurable retry (exponential backoff with jitter,
Retry-Afteraware) - Response caching (URLCache-backed, with optional synthetic TTL)
- Error mapping to the app-wide
AppErrortype
Full technical details live in your project at
docs/modules/Networking.md.What’s new in v2.2.0
- Swift 6 strict-concurrency clean:
HTTPClient,HTTPInterceptor,TokenProvider,HTTPRequest, andHTTPResponseare allSendable; protocol-typed properties use explicitany. URLSessionHTTPClient.applySyntheticTTLguards on a resolvable URL instead of force-unwrapping, so it stays safe under malformed request inputs. No public API change.
Purpose
Handles:- HTTP requests via a single
HTTPClient.send(_:)API - Cross-cutting concerns (auth tokens, app/telemetry headers, retry) through interceptors
- Consistent error handling by mapping transport and HTTP errors to
AppError
Core Abstractions
HTTPClient Protocol
HTTPRequest
HTTPRequest is a concrete Sendable value type — there is no per-endpoint protocol to conform to. Build one with a path and method, then layer headers, query, body, and an optional cache policy via the with… helpers.
HTTPMethod covers .get, .post, .put, .patch, .delete, .head, and .options.
HTTPResponse
send(_:) returns an HTTPResponse, not a decoded model — you decode response.data yourself.
Interceptors
Interceptors conform toHTTPInterceptor and are applied in order. Each can mutate the outgoing URLRequest (adapt) and/or vote on whether a failed attempt should be retried (shouldRetry).
- AuthInterceptor
- RetryInterceptor
- HeadersInterceptor
Bearer-token authentication
AuthInterceptor(tokenProvider:) adds an Authorization: Bearer {token} header:- Reads the current token from an injected
TokenProvider - Only sets the header if one is not already present (respects manual overrides)
- Does nothing when
currentToken()returnsnil - Returns
.noRetry— token-refresh logic stays in the Auth module to avoid coupling
Storage.KeychainTokenProvider, so tokens come from the secure iOS Keychain.RetryPolicy
RetryPolicy configures the backoff math used between attempts:
baseDelay * 2^(attempt-1), capped at maxDelay, then randomized by ±jitter.
Configuration
The boilerplate wires the client inCompositionRoot. Interceptors run in array order (headers → auth → retry):
UnconfiguredHTTPClient that fails fast instead of silently hitting a placeholder host.
Customization Examples
Add a custom request
BecauseHTTPRequest is a value type, a “custom request” is just a factory that returns one:
Add a custom interceptor
Error Handling
Transport failures and non-2xx responses are mapped toCore.AppError before they reach you:
Key Files
Dependencies
- Core —
AppError,AppLogger
Used By
- Auth — Supabase auth calls (
SupabaseAuthAPI,SessionManager) - AI —
ProxyLLMClientproxy requests to the Supabase Edge Function - Your services — any API client you build on top of
HTTPClient
Best Practices
Request Design
Request Design
- Build
HTTPRequestvalues with thewith…helpers - Decode
response.dataat the call site - Encode bodies with
JSONEncoderand setContent-Type - Clear naming (verb + noun) for request factories
Error Handling
Error Handling
- Catch
AppErrorfromsend(_:) - Let
RetryInterceptorhandle idempotent/transient retries - Surface user-facing copy via
AppError.localizedUserMessage
Interceptors
Interceptors
- Order matters — headers and auth before retry
- Keep each interceptor focused on one concern
- Test interceptors in isolation
- Return
.noRetryunless the interceptor owns retry logic
Learn More
Full Documentation
Complete Networking guide
Auth Module
Uses HTTPClient for API calls
AI Module
Uses HTTPClient for the proxy
Architecture
See the networking layer
Test Coverage
The Networking package ships with its ownNetworkingTests target (client, interceptor ordering, retry policy, caching, URL builder). It runs as one of the 12 package test targets in the suite of ~598 tests across 12 package test targets + the app test suites, executed in a single Boilerplate.xctestplan pass.