-
Notifications
You must be signed in to change notification settings - Fork 0
Error Handling
LichessSharp provides a comprehensive exception hierarchy for handling API errors. This guide covers exception types, retry strategies, and best practices.
All LichessSharp exceptions inherit from LichessException:
LichessException (base class)
├── LichessRateLimitException (HTTP 429)
├── LichessAuthenticationException (HTTP 401)
├── LichessAuthorizationException (HTTP 403)
├── LichessNotFoundException (HTTP 404)
└── LichessValidationException (HTTP 400)
The base exception for all Lichess API errors.
| Property | Type | Description |
|---|---|---|
| Message | string | Error description |
| StatusCode | HttpStatusCode? | HTTP status code if available |
| LichessError | string? | Error message from Lichess API |
| InnerException | Exception? | Underlying exception if any |
Example:
try
{
await client.Users.GetAsync("nonexistent-user");
}
catch (LichessException ex)
{
Console.WriteLine($"Error: {ex.Message}");
Console.WriteLine($"Status: {ex.StatusCode}");
Console.WriteLine($"Lichess says: {ex.LichessError}");
}Thrown when the API rate limit is exceeded (HTTP 429).
| Property | Type | Description |
|---|---|---|
| RetryAfter | TimeSpan? | Recommended wait time before retrying |
Example:
try
{
// Making many rapid requests
foreach (var username in usernames)
{
await client.Users.GetAsync(username);
}
}
catch (LichessRateLimitException ex)
{
Console.WriteLine($"Rate limited! Wait {ex.RetryAfter?.TotalSeconds ?? 60} seconds");
if (ex.RetryAfter.HasValue)
{
await Task.Delay(ex.RetryAfter.Value);
// Retry the request
}
}Thrown when authentication fails (HTTP 401). Usually means invalid or expired token.
Example:
try
{
using var client = new LichessClient("invalid_token");
await client.Account.GetProfileAsync();
}
catch (LichessAuthenticationException ex)
{
Console.WriteLine("Invalid token. Please generate a new one at:");
Console.WriteLine("https://lichess.org/account/oauth/token");
}Thrown when access is forbidden (HTTP 403). Usually means missing OAuth scopes.
| Property | Type | Description |
|---|---|---|
| RequiredScope | string? | The OAuth scope needed for this operation |
Example:
try
{
// Trying to send a message without msg:write scope
await client.Messaging.SendAsync("username", "Hello!");
}
catch (LichessAuthorizationException ex)
{
Console.WriteLine($"Missing permission. Required scope: {ex.RequiredScope}");
Console.WriteLine("Generate a new token with the required scope.");
}Thrown when a requested resource doesn't exist (HTTP 404).
Example:
try
{
var user = await client.Users.GetAsync("this-user-does-not-exist");
}
catch (LichessNotFoundException)
{
Console.WriteLine("User not found");
}Thrown when request validation fails (HTTP 400).
| Property | Type | Description |
|---|---|---|
| ValidationErrors | IReadOnlyDictionary<string, string[]>? | Field-specific validation errors |
Example:
try
{
await client.Challenges.CreateAsync("opponent", new ChallengeCreateOptions
{
ClockLimit = -1 // Invalid
});
}
catch (LichessValidationException ex)
{
Console.WriteLine($"Validation failed: {ex.Message}");
if (ex.ValidationErrors != null)
{
foreach (var (field, errors) in ex.ValidationErrors)
{
Console.WriteLine($" {field}: {string.Join(", ", errors)}");
}
}
}LichessSharp can automatically handle rate limits by waiting and retrying.
By default, the client will:
- Catch HTTP 429 responses
- Read the
Retry-Afterheader - Wait the specified duration
- Retry up to 3 times
// Default: auto-retry enabled with 3 attempts
using var client = new LichessClient(token);using var client = new LichessClient(token, new LichessClientOptions
{
// Disable automatic retries
AutoRetryOnRateLimit = false,
// Or configure retry behavior
AutoRetryOnRateLimit = true,
MaxRateLimitRetries = 5,
// For long-running bots, retry indefinitely
UnlimitedRateLimitRetries = true
});If you disable auto-retry, handle rate limits manually:
var options = new LichessClientOptions { AutoRetryOnRateLimit = false };
using var client = new LichessClient(token, options);
async Task<User> GetUserWithRetry(string username, int maxRetries = 3)
{
for (int attempt = 1; attempt <= maxRetries; attempt++)
{
try
{
return await client.Users.GetAsync(username);
}
catch (LichessRateLimitException ex)
{
if (attempt == maxRetries) throw;
var delay = ex.RetryAfter ?? TimeSpan.FromSeconds(60);
Console.WriteLine($"Rate limited. Waiting {delay.TotalSeconds}s (attempt {attempt}/{maxRetries})");
await Task.Delay(delay);
}
}
throw new InvalidOperationException("Unreachable");
}LichessSharp automatically retries on transient network failures (DNS errors, connection timeouts).
- Enabled by default
- Retries up to 3 times
- Exponential backoff with jitter (1s, 2s, 4s...)
- Maximum delay capped at 30 seconds
using var client = new LichessClient(token, new LichessClientOptions
{
// Disable transient retry
EnableTransientRetry = false,
// Or customize
EnableTransientRetry = true,
MaxTransientRetries = 5,
TransientRetryBaseDelay = TimeSpan.FromSeconds(2),
TransientRetryMaxDelay = TimeSpan.FromMinutes(1)
});Streaming endpoints require special handling for long-running connections.
async Task StreamWithReconnect(CancellationToken ct)
{
while (!ct.IsCancellationRequested)
{
try
{
await foreach (var evt in client.Bot.StreamEventsAsync(ct))
{
ProcessEvent(evt);
}
}
catch (HttpRequestException ex) when (!ct.IsCancellationRequested)
{
Console.WriteLine($"Connection lost: {ex.Message}");
Console.WriteLine("Reconnecting in 5 seconds...");
await Task.Delay(TimeSpan.FromSeconds(5), ct);
}
catch (OperationCanceledException)
{
break;
}
}
}Always use CancellationToken for clean shutdown:
using var cts = new CancellationTokenSource();
Console.CancelKeyPress += (_, e) =>
{
e.Cancel = true;
Console.WriteLine("Shutting down...");
cts.Cancel();
};
try
{
await foreach (var evt in client.Bot.StreamEventsAsync(cts.Token))
{
// Process events
}
}
catch (OperationCanceledException)
{
Console.WriteLine("Stream stopped gracefully");
}using LichessSharp;
using LichessSharp.Exceptions;
async Task<bool> SafeCreateChallenge(ILichessClient client, string opponent)
{
try
{
var challenge = await client.Challenges.CreateAsync(opponent, new ChallengeCreateOptions
{
Rated = true,
ClockLimit = 300,
ClockIncrement = 3
});
Console.WriteLine($"Challenge created: {challenge.Id}");
return true;
}
catch (LichessNotFoundException)
{
Console.WriteLine($"User '{opponent}' not found");
return false;
}
catch (LichessAuthorizationException ex)
{
Console.WriteLine($"Permission denied. Need scope: {ex.RequiredScope ?? "challenge:write"}");
return false;
}
catch (LichessValidationException ex)
{
Console.WriteLine($"Invalid challenge options: {ex.Message}");
return false;
}
catch (LichessRateLimitException ex)
{
Console.WriteLine($"Too many requests. Try again in {ex.RetryAfter?.TotalSeconds ?? 60}s");
return false;
}
catch (LichessException ex)
{
Console.WriteLine($"Lichess error ({ex.StatusCode}): {ex.LichessError ?? ex.Message}");
return false;
}
catch (HttpRequestException ex)
{
Console.WriteLine($"Network error: {ex.Message}");
return false;
}
}try
{
// API call
}
catch (LichessNotFoundException)
{
// Handle 404 specifically
}
catch (LichessRateLimitException)
{
// Handle rate limiting
}
catch (LichessException)
{
// Handle other Lichess errors
}
catch (Exception)
{
// Handle unexpected errors
}Always pass cancellation tokens to allow graceful shutdown:
using var cts = new CancellationTokenSource(TimeSpan.FromMinutes(5));
await client.Games.ExportAsync(gameId, cancellationToken: cts.Token);var options = new LichessClientOptions
{
DefaultTimeout = TimeSpan.FromSeconds(60), // Regular requests
StreamingTimeout = Timeout.InfiniteTimeSpan // Streaming (default)
};catch (LichessException ex)
{
logger.LogError(ex, "Lichess API error: Status={Status}, Error={Error}",
ex.StatusCode, ex.LichessError);
throw;
}Some methods return null instead of throwing:
var eval = await client.Analysis.GetCloudEvaluationAsync(fen);
if (eval == null)
{
Console.WriteLine("Position not found in cloud database");
}- Streaming Events Guide - Stream handling patterns
- OAuth API - Authentication
- Quick Reference - Common patterns