Error Handling (SDK Layer)#

SDK Layer failures fall into the same three categories as the rest of the stack: retryable, unrecoverable, and OAuth protocol. HaapiManager and OAuthTokenManager surface them with one important addition over the Driver Layer — OAuth protocol errors are returned as ErrorTokenResponse, not thrown.

For the categorization and per-subtype guidance, see Error Handling . For the underlying retryable / unrecoverable mechanics shared with the Driver Layer, see Error Handling (Driver Layer) .

Decision Table#

When an SDK call fails, classify the surface and respond accordingly:

SurfaceWhat it meansWhat to do
iOS HaapiError with recoverySuggestion == .retryable(let condition) / Android IdsvrHaapiException.Retryable / RN HaapiError with recovery.kind === 'retryable'Transient — network blip, fresh DPoP nonce required, 5xx with Retry-AfterRetry per the condition (Now or WhenAppForeground)
iOS recoverySuggestion == .newHaapiFlow / RN recovery.kind === 'newHaapiFlow'The current flow is no longer usable but a fresh one will succeedConstruct a new manager and start over
iOS recoverySuggestion == .nonRecoverable(let action) / Android IdsvrHaapiException.Unrecoverable / RN recovery.kind === 'unrecoverable'Misconfiguration, unsupported platform, or access deniedSurface the cause to the user or developer; do not auto-retry
TokenResponse with ErrorTokenResponse carrying an OAuth error codeServer understood and explicitly rejected (e.g., invalid_grant, invalid_dpop_proof)Inspect the error code — see “Common OAuth Error Codes” below

Retryable and Unrecoverable Errors#

Completion handlers surface Error values. Cast to HaapiError for the enum, then consult recoverySuggestion for retry guidance:

private func handleError(_ error: Error) {
    guard let haapiError = error as? HaapiError else { return }
    switch haapiError.recoverySuggestion {
    case .retryable(let condition):
        // condition is .now (retry immediately with exponential backoff)
        // or .whenAppForeground (defer until the app is foregrounded)
        break
    case .newHaapiFlow:
        // start a fresh HAAPI flow
        break
    case .nonRecoverable(let action):
        // action is .modifyConfiguration (developer to fix client settings),
        // .invalidPlatform (device cannot satisfy the protocol),
        // or .introspectCause (inspect the wrapped cause for follow-up)
        break
    }
}

Specific HaapiError cases — attestationFailure, haapiTokenManagerIsClosed, invalidStatusCode, serverError, dpopKeyCreationFailure, dpopProofFailure — can be matched directly when their context matters:

switch haapiError {
case .attestationFailure(let cause):
    // attestation rejected — surface to user; consider DCR fallback
    break
case .haapiTokenManagerIsClosed:
    // construct a new manager and start over
    break
case .dpopKeyCreationFailure, .dpopProofFailure:
    // rare; rebuild configuration with .p256 and re-authenticate
    break
default:
    break
}

Was this helpful?