Skip to main content

Exception hierarchy

Every typed exception thrown by the SDK derives from Ekso.Sdk.Exceptions.EksoException. The base class carries:
  • RequestId — server-side correlation id, included on supported failures.
  • StatusCode — HTTP status code, when the failure had one.
Subclasses encode the failure shape so you can branch on the type:
EksoException (abstract)
├── EksoAuthException        — credentials rejected / refresh failed
├── EksoRateLimitException   — 429, with RetryAfter
├── EksoValidationException  — 400/422, with FieldErrors map
├── EksoNetworkException     — DNS/TCP/TLS/timeout — typically transient
└── EksoApiException         — generic non-2xx that didn't fit a more specific shape

Catching by shape

Order catches from specific to general:
using Ekso.Sdk.Exceptions;

try
{
    var result = await client.Api.Item.PostAsync(item);
}
catch (EksoValidationException ex)
{
    foreach (var (field, errors) in ex.FieldErrors)
        Console.Error.WriteLine($"  {field}: {string.Join(", ", errors)}");
    return 6; // CLI exit code: validation
}
catch (EksoRateLimitException ex)
{
    await Task.Delay(ex.RetryAfter);
    // ... retry
}
catch (EksoAuthException ex) when (ex.Reason == AuthFailureReason.Expired)
{
    // Re-run interactive auth or fetch a fresh API key
}
catch (EksoNetworkException)
{
    // Transient — exponential backoff is appropriate
}
catch (EksoApiException ex)
{
    Console.Error.WriteLine($"API error {ex.StatusCode}: {ex.Message}");
    Console.Error.WriteLine($"Body: {ex.Body}");
}
Catching EksoException alone is fine for “log it and surface” paths, but loses the shape information you need to recover.

EksoAuthException

Thrown when auth cannot be recovered automatically. The Reason enum tells you why:
ReasonMeaningRecovery
ExpiredAccess token expired and refresh failed (or no refresh token).Re-run interactive flow / fetch a fresh API key.
InvalidCredential malformed or unknown.Check credential source. Don’t retry.
RevokedCredential explicitly revoked.Mint a new one. Don’t retry.
FlowTimeoutOAuth device-code flow timed out before user approved.Restart the flow.
AccessDeniedUser clicked “Deny” on the consent screen.Don’t retry without user intent.
A 401 from a regular API call doesn’t always surface as EksoAuthException — the SDK first attempts a refresh. You only see this exception when refresh itself fails.

EksoRateLimitException

Thrown on HTTP 429. Always carries a populated RetryAfter (TimeSpan) reflecting the server’s Retry-After header. Honour it — additional rapid calls will continue to 429.
catch (EksoRateLimitException ex)
{
    await Task.Delay(ex.RetryAfter);
    return await operation(); // try again
}
For batch ingestion, prefer to not hit rate-limited endpoints in tight loops — pace your requests, or use --batch flavoured operations where they exist.

EksoValidationException

Thrown on HTTP 400/422 when the body fails validation. FieldErrors is a map of field name → list of error messages, so you can highlight specific inputs in a form:
catch (EksoValidationException ex)
{
    foreach (var (field, errors) in ex.FieldErrors)
        Console.Error.WriteLine($"  {field}: {string.Join(", ", errors)}");
}
Field names use the JSON serialization (camelCase) — name, defaultValue, etc.

EksoNetworkException

Wraps the underlying transport failure (HttpRequestException, TaskCanceledException, etc.) as InnerException. These are typically transient — a brief retry with exponential backoff is reasonable:
async Task<T> WithRetry<T>(Func<Task<T>> op, int max = 3)
{
    for (var i = 0; ; i++)
    {
        try { return await op(); }
        catch (EksoNetworkException) when (i < max)
        {
            await Task.Delay(TimeSpan.FromMilliseconds(250 * Math.Pow(2, i)));
        }
    }
}
Don’t retry other exception types blindly — EksoValidationException and EksoAuthException will fail identically every time.

EksoApiException

The catch-all for non-2xx responses that don’t match a more specific shape. Inspect Body (a JsonElement) for the raw error payload:
catch (EksoApiException ex)
{
    if (ex.Body.TryGetProperty("error", out var err))
        Console.Error.WriteLine(err.GetString());
}
Future SDK versions will narrow more responses into specific exception types — code that catches the specific subclasses will continue to work; code that only catches EksoApiException may need to add handlers as the surface evolves.

What about Kiota’s ApiException?

The SDK currently surfaces Kiota’s own ApiException verbatim for some endpoints — it’s the lower-level transport exception used internally. You can catch it as a sibling of EksoException if you want to be exhaustive:
catch (Microsoft.Kiota.Abstractions.ApiException ex)
{
    Console.Error.WriteLine($"Kiota: {ex.Message}");
}
Broader mapping of Kiota’s ApiException into EksoApiException is on the roadmap for a future minor version.

See also

  • Authentication — how to set up auth so refresh works seamlessly.
  • CLI/SDK marker — server-side gating that produces 403 responses for some operations.