> ## Documentation Index
> Fetch the complete documentation index at: https://ekso.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Error handling

> The SDK exception hierarchy. Each shape encodes a failure mode you can react to differently.

## 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:

```text theme={null}
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:

```csharp theme={null}
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:

| Reason         | Meaning                                                        | Recovery                                         |
| -------------- | -------------------------------------------------------------- | ------------------------------------------------ |
| `Expired`      | Access token expired and refresh failed (or no refresh token). | Re-run interactive flow / fetch a fresh API key. |
| `Invalid`      | Credential malformed or unknown.                               | Check credential source. Don't retry.            |
| `Revoked`      | Credential explicitly revoked.                                 | Mint a new one. Don't retry.                     |
| `FlowTimeout`  | OAuth device-code flow timed out before user approved.         | Restart the flow.                                |
| `AccessDenied` | User 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.

```csharp theme={null}
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:

```csharp theme={null}
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:

```csharp theme={null}
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:

```csharp theme={null}
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:

```csharp theme={null}
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](/sdk/authentication)** — how to set up auth so refresh works seamlessly.
* **[CLI/SDK marker](/sdk/cli-sdk-marker)** — server-side gating that produces 403 responses for some operations.
