Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.synheart.ai/llms.txt

Use this file to discover all available pages before exploring further.

The SDK exposes a sealed SynheartAuthError hierarchy in Dart (mirrored as enum-style errors in Kotlin and Swift). Every error has a stable code that maps 1:1 to RFC-AUTH-MOBILE-0001 §11.

Taxonomy

CodeDart classTriggerSDK behaviour
NETWORK_ERRORNetworkError(message)Challenge or register HTTP call failedRetry with backoff (RFC §12.1).
CHALLENGE_EXPIREDChallengeExpiredServer rejects stale challengeRe-fetch a new challenge.
ATTESTATION_UNAVAILABLEAttestationUnavailableDevice/OS doesn’t support attestationSurface to developer; cannot proceed.
ATTESTATION_FAILED(subset of ServerError)Attestation provider rejectsRetry once; then surface.
KEY_INVALIDATEDKeyInvalidatedHardware key no longer usableTransition to keyInvalid; next registerDevice wipes and restarts.
KEYSTORE_ERRORStorageErrorAndroid Keystore op failedSurface with platform details.
SECURE_ENCLAVE_ERRORStorageErroriOS Secure Enclave op failedSurface with platform details.
SIGNING_FAILEDCryptoErrorSignature computation failedReturn immediately; non-recoverable in-request.
DEVICE_REVOKEDServerErrorServer marks device inactiveForce re-registration via resetDeviceIdentity.
CLOCK_SKEWClockSkewTimestamp freshness rejectedApply offset correction; retry.
ROTATION_FAILEDServerErrorKey rotation request rejectedKeep old key active; retry later.
NONCE_REPLAYServerErrorServer rejects duplicate nonceGenerate new nonce and retry once.
ALREADY_REGISTEREDAlreadyRegisteredregisterDevice called when state is registeredNo-op fast-path; treat as success.
NOT_REGISTEREDNotRegisteredsignRequest called before registrationCall registerDevice first.
NOT_CONFIGUREDNotConfiguredAPI used before configure(baseUrl)Call configure once at app launch.
REGISTRATION_IN_PROGRESSRegistrationInProgressConcurrent registerDeviceWait for the in-flight call.
CRYPTO_ERRORCryptoError(message)Generic crypto failureInspect message; usually surfaced to developer.
STORAGE_ERRORStorageError(message)Keychain / EncryptedSharedPreferences failureInspect message; surface.
INVALID_STATE_TRANSITIONInvalidStateTransition(from, to)Programmer error; state machine misuseInspect call site.
(any other server code)ServerError(code, serverMessage)Catch-all for server-defined codesInspect; default surface to developer.
SynheartAuthError.fromCode(code, msg?) maps platform-channel error codes back to the typed exception in Dart.

Key invalidation detection (RFC §11.1)

Hardware keys can become unusable due to:
  • OS updates that rotate Secure Enclave / Keystore root keys.
  • Biometric enrollment changes.
  • Device security state changes (PIN reset, factory reset).
  • Backup/restore between devices.
PlatformDetection
AndroidKeyPermanentlyInvalidatedException on Cipher.init/Signature.initSign. UnrecoverableKeyException when the alias no longer resolves.
iOSerrSecItemNotFound from SecKeyCopyExternalRepresentation or SecKeyCreateSignature. errSecAuthFailed when the biometric context can’t authenticate. LAError.biometryLockout for biometry-bound keys after too many failures.
On detection:
  1. The SDK emits KEY_INVALIDATED (Dart: KeyInvalidated).
  2. State transitions registered → keyInvalid.
  3. The next registerDevice(appId) call wipes local state and runs the full registration flow.
The SDK never silently retries with the invalid key.

Clock skew correction (RFC §11.2)

Mobile clocks drift. The SDK can’t force NTP, so it learns the offset from the server.

Server contract

When rejecting a request with CLOCK_SKEW, the response includes:
{
  "error": "CLOCK_SKEW",
  "server_timestamp": 1709312345,
  "message": "Request timestamp too far from server time"
}

SDK behaviour

Future<void> correctClockSkew(double serverTimestamp) {
  final localTimestamp = DateTime.now().millisecondsSinceEpoch / 1000;
  final offsetMs = ((serverTimestamp - localTimestamp) * 1000).round();
  // persist offsetMs as clock_offset_ms
  // on next sign: adjusted_ts = local_ts + clock_offset_ms / 1000
}
The SDK exposes correctClockSkew(serverTimestamp) for hosts to call when they receive a CLOCK_SKEW error from any Synheart endpoint. Subsequent signatures use the corrected timestamp automatically. The persisted clock_offset_ms survives app restarts. Per RFC §11.2, the SDK SHOULD periodically re-validate the offset by comparing with X-Synheart-Server-Time from successful responses (server-supplied; not currently emitted by all services).

Retry policy (RFC §12)

Challenge + registration

delay = min(base * 2^attempt + random(0, jitter), max_delay)
base   = 1 second
jitter = 500 ms
max    = 30 seconds
attempts = 5
Always re-fetch a new challenge after any register failure (challenges are single-use).

Signing

Signing failures are non-recoverable in-request — return error immediately. If KEY_INVALIDATED is detected, transition to keyInvalid and trigger re-registration before the next request.

Key rotation

Rotation failures MUST NOT block normal request signing. Backoff: base 60s, max 1 hour, attempts 3 per cycle. After max attempts, log warning and retry on next app launch.

Logging policy

Per RFC §13:
Allowed (prod)Never (prod)
app_idRaw signatures
device_id (truncated/hashed)Raw request bodies
Error codesAttestation proof payloads
Timing metricsPublic key material (debug builds only with opt-in)
Rotation eventsClock offset values
State transitions
InvalidStateTransition should be logged at error level — it indicates a programmer bug.
  • State MachineKeyInvalidated and InvalidStateTransition semantics.
  • Registration — failure paths during the handshake.
  • SigningCLOCK_SKEW, NONCE_REPLAY recovery.