Error Handling
The Reasoning Layer SDK uses a typed error hierarchy that lets you catch specific error conditions with instanceof checks. All errors extend a common base class, and the SDK handles rate limiting and retries automatically.
Error hierarchy
ReasoningLayerError (base, extends Error) ├── ApiError (HTTP 4xx/5xx: .status, .errorCode, .body, .headers) │ ├── BadRequestError (400) │ ├── AuthenticationError (401) │ ├── ForbiddenError (403) │ ├── NotFoundError (404) │ ├── ConstraintViolationError (409: .termId, .feature, .constraint) │ ├── RateLimitError (429: .retryAfter, .limit, .remaining) │ └── InternalServerError (500+) ├── TimeoutError (.timeoutMs) ├── ValidationError (.field) └── NetworkErrorCatching errors
Catch all SDK errors
Every error thrown by the SDK extends ReasoningLayerError:
import { ReasoningLayerError } from '@kortexya/reasoninglayer';
try { await client.sorts.getSort(sortId);} catch (error) { if (error instanceof ReasoningLayerError) { console.error("SDK error:", error.message); }}Catch specific HTTP errors
Each HTTP status code has a dedicated error class:
import { ApiError, AuthenticationError, ForbiddenError, BadRequestError, NotFoundError, ConstraintViolationError, RateLimitError, InternalServerError,} from '@kortexya/reasoninglayer';
try { await client.terms.createTerm(request);} catch (error) { if (error instanceof NotFoundError) { console.error("Sort not found:", error.message); // error.status === 404 } else if (error instanceof BadRequestError) { console.error("Invalid request:", error.message); // error.status === 400 // error.body contains the parsed response body } else if (error instanceof ConstraintViolationError) { console.error("Constraint violated:", error.message); // error.status === 409 // error.termId — the term that violated the constraint // error.feature — the feature that violated the constraint // error.constraint — description of the violated constraint } else if (error instanceof AuthenticationError) { console.error("Not authenticated:", error.message); // error.status === 401 } else if (error instanceof ForbiddenError) { console.error("Not authorized:", error.message); // error.status === 403 } else if (error instanceof RateLimitError) { console.error("Rate limited:", error.message); // error.retryAfter — seconds to wait (from Retry-After header) // error.limit — max requests in the current window // error.remaining — requests remaining } else if (error instanceof InternalServerError) { console.error("Server error:", error.status, error.message); } else if (error instanceof ApiError) { // Catch-all for other HTTP errors (e.g., 422) console.error(`HTTP ${error.status}:`, error.message); console.error("Error code:", error.errorCode); console.error("Response body:", error.body); }}Catch non-HTTP errors
import { TimeoutError, ValidationError, NetworkError,} from '@kortexya/reasoninglayer';
try { await client.inference.backwardChain(request);} catch (error) { if (error instanceof TimeoutError) { console.error(`Request timed out after ${error.timeoutMs}ms`); } else if (error instanceof ValidationError) { console.error(`Validation failed: ${error.message}`); if (error.field) { console.error(` Field: ${error.field}`); } } else if (error instanceof NetworkError) { console.error("Network failure:", error.message); // error.cause may contain the original fetch error }}ApiError properties
All HTTP errors (subclasses of ApiError) carry these properties:
| Property | Type | Description |
|---|---|---|
status | number | HTTP status code |
errorCode | string | undefined | Backend error code from response body error field |
body | unknown | Parsed response body (JSON) |
headers | Headers | Response headers |
message | string | Human-readable error message |
ConstraintViolationError
The 409 error carries structured information about which constraint was violated:
try { await client.terms.createTerm(request);} catch (error) { if (error instanceof ConstraintViolationError) { console.error("Term ID:", error.termId); console.error("Feature:", error.feature); console.error("Constraint:", error.constraint); // These are parsed from the response body's `details` field }}Automatic retry
The SDK automatically retries requests on certain status codes with exponential backoff and jitter.
Default retry behavior
| Condition | Retried? | Notes |
|---|---|---|
| HTTP 429 | Always | Respects Retry-After header when present |
| HTTP 503 | Optional | Controlled by retryOn503 in ClientConfig |
| Other 5xx | Never | Only 503 is retried when enabled |
| HTTP 4xx | Never | Client errors are not retried |
| Network error | Never | Connection failures are not retried |
| Timeout | Never | Timeouts are not retried |
Configuration
const client = new ReasoningLayerClient({ baseUrl: 'https://platform.ovh.reasoninglayer.ai', tenantId: 'your-tenant-uuid', auth: { mode: 'cookie' }, maxRetries: 3, // Maximum retry attempts (default: 3) retryOn503: true, // Also retry on 503 Service Unavailable (default: false)});When the server responds with a Retry-After header, the SDK waits that many seconds before retrying. When the header is absent, the SDK uses exponential backoff with jitter.
After all retries are exhausted, the SDK throws the appropriate error (e.g., RateLimitError for 429).
Rate limit headers
The backend sends rate limit information in response headers:
| Header | Description |
|---|---|
Retry-After | Seconds to wait before retrying (on 429) |
X-RateLimit-Limit | Maximum requests allowed in the current window |
X-RateLimit-Remaining | Requests remaining in the current window |
The RateLimitError class exposes these values:
try { await client.sorts.listSorts();} catch (error) { if (error instanceof RateLimitError) { console.log(`Wait ${error.retryAfter} seconds`); console.log(`Limit: ${error.limit}, Remaining: ${error.remaining}`); }}Timeout configuration
Client-level default
const client = new ReasoningLayerClient({ baseUrl: 'https://platform.ovh.reasoninglayer.ai', tenantId: 'your-tenant-uuid', auth: { mode: 'cookie' }, timeoutMs: 15000, // 15 second default timeout (default: 30000)});Server-side timeouts
Some inference endpoints accept a timeoutMs field on the request body. This is a server-side timeout that limits how long the backend spends on proof search. When the server timeout fires, the backend returns whatever results have been found so far (partial results), rather than throwing an error:
const result = await client.inference.backwardChain({ goal: psi("complex_query", { x: "?X", }), timeoutMs: 5000, // Server stops searching after 5 seconds});
// result.solutions may be partial if the server timeout firedThe client-level timeoutMs (set on ClientConfig) controls when the HTTP request is aborted. Set the client-level timeout higher than the server-side timeoutMs on the request body to ensure you receive partial results rather than a client timeout error.