Exception Hierarchy
All exceptions thrown by the SDK when API calls fail. Every exception includes the HTTP status code, a Flare error code, and a server-assigned request ID for correlation.
Namespace: Zeridion.Flare · Assembly: Zeridion.Flare.dll
Hierarchy
FlareApiException
The base exception for all Flare API errors. Catch this to handle any API failure generically.
public class FlareApiException : Exception
{
public int StatusCode { get; }
public string ErrorCode { get; }
public string? RequestId { get; }
}
| Property | Type | Description |
|---|---|---|
StatusCode | int | HTTP status code returned by the API (e.g. 400, 500). |
ErrorCode | string | Flare error code (e.g. "job_not_found", "invalid_request"). |
RequestId | string? | Server-assigned request ID for support and debugging. |
The Message property (inherited from Exception) contains the human-readable error description from the API.
Thrown for any non-success HTTP response that does not match one of the specific subtypes below (e.g. 400 Bad Request, 500 Internal Server Error).
FlareAuthenticationException
Thrown when the API returns 401 Unauthorized — the API key is invalid, expired, or missing.
public sealed class FlareAuthenticationException : FlareApiException
{
// StatusCode is always 401
}
When it is thrown:
- The
ApiKeyinZeridionFlareOptionsis incorrect or has been revoked - The
Authorizationheader is missing (SDK misconfiguration)
Common error codes: "unauthorized"
try
{
await jobs.EnqueueAsync<SendEmail>(payload);
}
catch (FlareAuthenticationException ex)
{
logger.LogCritical("Invalid API key: {Error} (request: {RequestId})",
ex.Message, ex.RequestId);
// Alert ops — this is a configuration issue, not a transient error
}
FlareNotFoundException
Thrown when the API returns 404 Not Found — the job or resource does not exist.
public sealed class FlareNotFoundException : FlareApiException
{
// StatusCode is always 404
}
When it is thrown:
ContinueWithAsyncreferences a parent job that does not exist (API returns 422parent_not_found)CancelAsyncorRetryAsyncis called with a job ID that does not exist
GetStatusAsync does not throw FlareNotFoundException. It returns null when the job does not exist, allowing callers to check without try/catch.
Common error codes: "job_not_found"
try
{
await jobs.ContinueWithAsync<SendReceipt>(parentJobId, payload);
}
catch (FlareNotFoundException ex)
{
logger.LogWarning("Parent job {ParentId} not found: {Error}",
parentJobId, ex.Message);
}
FlareConflictException
Thrown when the API returns 409 Conflict — a state conflict or duplicate was detected.
public sealed class FlareConflictException : FlareApiException
{
// StatusCode is always 409
}
When it is thrown:
- An
IdempotencyKeymatches an existing job (duplicate prevention) - A state transition is invalid (e.g. cancelling an already-completed job, though
CancelAsynccatches this and returnsfalseinstead) - A worker mismatch during acknowledgement
Common error codes: "idempotency_key_reuse", "invalid_state", "worker_mismatch"
try
{
await jobs.EnqueueAsync<ProcessPayment>(payload, new JobOptions
{
IdempotencyKey = $"payment:{orderId}"
});
}
catch (FlareConflictException ex)
{
logger.LogInformation("Duplicate job prevented by idempotency key: {Error}",
ex.Message);
// This is expected behavior — the job already exists
}
CancelAsync and RetryAsync do not throw FlareConflictException on 409 — they return false instead, making them safe to call without try/catch.
FlareQuotaException
Thrown when the API returns 402 Payment Required — the daily job-creation quota for the project has been exhausted.
public sealed class FlareQuotaException : FlareApiException
{
public int Limit { get; }
public int Used { get; }
public DateTimeOffset? ResetAt { get; }
// StatusCode is always 402
}
| Property | Type | Description |
|---|---|---|
Limit | int | Maximum jobs allowed per day (from X-Quota-Limit). |
Used | int | Jobs already consumed in the current window (from X-Quota-Used). |
ResetAt | DateTimeOffset? | When the quota window resets (from X-Quota-Reset). |
When it is thrown:
- The project has hit its daily job-creation cap for the current billing plan.
- The tenant is in a non-
Activebilling state (e.g.past_due,cancelled) and the quota middleware is blocking writes.
Common error codes: "quota_exceeded", "payment_required"
try
{
await jobs.EnqueueAsync<SendEmail>(payload);
}
catch (FlareQuotaException ex)
{
logger.LogWarning(
"Daily quota exhausted: {Used}/{Limit} used, resets at {ResetAt}",
ex.Used, ex.Limit, ex.ResetAt);
// Surface to the user, or fall back to a degraded path —
// retrying before ResetAt will hit the same 402.
}
FlareQuotaException is distinct from FlareRateLimitException (429). Quota is a daily budget tied to your billing plan; rate limit is a short-window request throttle. Treat quota as a billing concern (upgrade plan, wait for reset, or alert ops); treat rate limit as a transient backpressure signal (back off and retry).
FlareRateLimitException
Thrown when the API returns 429 Too Many Requests — the rate limit for your API key has been exceeded.
public sealed class FlareRateLimitException : FlareApiException
{
public int Limit { get; }
public int Remaining { get; }
public DateTimeOffset? ResetAt { get; }
public TimeSpan? RetryAfter { get; }
}
| Property | Type | Description |
|---|---|---|
Limit | int | Maximum requests allowed per window (from X-RateLimit-Limit). |
Remaining | int | Remaining requests in the current window (from X-RateLimit-Remaining). |
ResetAt | DateTimeOffset? | When the rate limit window resets (from X-RateLimit-Reset). |
RetryAfter | TimeSpan? | Suggested wait before retrying, parsed from the standard HTTP Retry-After header per RFC 7231 §7.1.3. Both delta-seconds and HTTP-date forms are accepted. null when the server omits the header. Prefer this over computing a delay from ResetAt — the server already capped it to a sane bound. |
When it is thrown:
- Your API key has exceeded its rate limit tier
Common error codes: "rate_limit_exceeded"
try
{
await jobs.EnqueueAsync<SendEmail>(payload);
}
catch (FlareRateLimitException ex)
{
logger.LogWarning(
"Rate limited: {Limit} req/window, {Remaining} remaining, resets at {ResetAt}",
ex.Limit, ex.Remaining, ex.ResetAt);
// Prefer the server-supplied Retry-After hint when present;
// fall back to ResetAt, then a conservative default.
var delay = ex.RetryAfter
?? (ex.ResetAt.HasValue ? ex.ResetAt.Value - DateTimeOffset.UtcNow : TimeSpan.FromSeconds(5));
if (delay > TimeSpan.Zero)
await Task.Delay(delay);
}
Comprehensive catch pattern
Handle all exception types from most specific to least specific:
try
{
var jobId = await jobs.EnqueueAsync<ProcessOrder>(orderPayload, new JobOptions
{
IdempotencyKey = $"order:{order.Id}"
});
logger.LogInformation("Enqueued job {JobId}", jobId);
}
catch (FlareRateLimitException ex)
{
logger.LogWarning("Rate limited, retry after {RetryAfter}", ex.RetryAfter ?? TimeSpan.FromSeconds(5));
// Back off and retry, or queue locally
}
catch (FlareQuotaException ex)
{
logger.LogError(
"Daily quota exhausted ({Used}/{Limit}); resets at {ResetAt}",
ex.Used, ex.Limit, ex.ResetAt);
// Surface to user / alert ops — retrying before ResetAt will hit 402 again.
throw;
}
catch (FlareConflictException)
{
logger.LogInformation("Duplicate order job — already enqueued");
// Safe to ignore — idempotency key prevented duplicate work
}
catch (FlareAuthenticationException ex)
{
logger.LogCritical("API key invalid: {Error}", ex.Message);
// Configuration error — alert immediately
throw;
}
catch (FlareApiException ex)
{
logger.LogError("Flare API error {StatusCode}: {Error} (request: {RequestId})",
ex.StatusCode, ex.Message, ex.RequestId);
// Unexpected API error — log and decide whether to retry
}
See also
- Error handling guide — patterns for handling failures in production
- Idempotency guide — preventing duplicate work with idempotency keys
- IJobClient — the methods that throw these exceptions