Error handling
All SDK calls throw a typed EkycException subclass on platform errors. Catch the type you care about, and let unknown failures bubble.
try {
final r = await client.faceVerifyImages(imageA: a, imageB: b);
// success
} on EkycValidationException catch (e) {
// 400 / 422 — user-facing input problem
print('${e.type}: ${e.message}');
} on EkycAuthException catch (e) {
// 401 — token expired or invalid; ask the user to retry
} on EkycRateLimitException catch (e) {
await Future.delayed(e.retryAfter ?? const Duration(seconds: 1));
// retry
} on EkycFeatureDisabledException {
// the tenant doesn't have this feature on their plan
} on EkycException catch (e) {
// generic fallback — log e.requestId and surface a friendly message
}
Typed subclasses
| Subclass | When it fires | HTTP statuses |
|---|---|---|
EkycValidationException | Input rejected by the gateway or upstream service (bad image, missing field, etc.) | 400, 422 |
EkycAuthException | Token expired, missing, or invalid; tenant suspended / archived | 401, 403 |
EkycRateLimitException | Rate cap exceeded. retryAfter is populated from the Retry-After header. | 429 |
EkycFeatureDisabledException | The feature isn't enabled on the tenant's current plan, or the tenant has hit its hard cap. | 403 |
EkycException (base) | Anything else the interceptor sees with the standard { "error": { "type", "message", "request_id" } } envelope. | any |
Fields
| Field | Type | Notes |
|---|---|---|
type | String | The gateway's error type (validation_error, unauthorized, etc.). Maps to error.type in the wire envelope. |
message | String | Human-readable summary. Safe to surface to users for validation_error; otherwise log only. |
requestId | String | Always populated. Quote in support tickets. |
statusCode | int | HTTP status. |
docUrl | String? | Link to a docs page describing the error type, if available. |
retryAfter | Duration? | EkycRateLimitException only. |
The v1 EkycError.code field is renamed to type (matches the wire envelope's actual field name). The v1 .param field is gone — when validation fails, the offending field is in message.
User-facing copy: userMessage
Added in v2.2.0. Every EkycException has a .userMessage extension getter that maps the structured type field to a short end-user-safe English sentence. Drop it straight into a snackbar or toast — the fallback for unknown types deliberately does not leak .message, so it's safe to render without a per-error allowlist.
try {
await client.faceVerifyImages(imageA: a, imageB: b);
} on EkycException catch (e) {
showSnackBar(e.userMessage);
log('${e.type}: ${e.message}', requestId: e.requestId);
}
.message and .requestId stay logged separately for diagnostics — .message can contain things like connect ECONNREFUSED 10.0.0.1 that are useful for support tickets but unsafe to show users.
English-only in v2.2.0; a localized-copy override hook is planned for a future revision.
v2.1.2 fix: callers can now on EkycException catch directly
Versions before v2.1.2 leaked Dio's internal envelope: the typed exception was attached as DioException.error and callers had to pattern-match on DioException catch (e) { if (e.error is EkycException) ... }. v2.1.2 fixed this — the _ekycCall<T> wrapper inside each public method strips the DioException envelope when the inner cause is an EkycException. The contract above (on EkycValidationException catch (e) etc.) works as documented.
Non-EkycException DioExceptions still propagate as DioException. Connection timeouts, DNS failures, TLS errors — these are network-level failures and the SDK doesn't fabricate a fake EkycException for them. Handle them via standard Dio idioms:
try {
final r = await client.faceVerifyImages(imageA: a, imageB: b);
} on EkycException catch (e) {
// platform-level error
} on DioException catch (e) {
// network-level error
if (e.type == DioExceptionType.connectionTimeout) { … }
}
tokenProvider failures
If your tokenProvider callback throws (e.g. your backend is unreachable), the SDK doesn't wrap it — the exception propagates as-is so you can match on its real type. The SDK does not retry your tokenProvider. That's your responsibility; add retry logic, circuit breakers, etc. inside the callback before returning to the SDK.
See Errors and retries for the platform-side error reference (every error.type value with its meaning and recommended client action).