Reference
Error Handling
Which HTTP status raises which class in each SDK, what an error carries, and which failures the SDKs retry before you see them.
Every LevelFour SDK maps an HTTP status code to its own typed error class, and every one of those classes inherits from a base class you can catch when the specific type does not matter. Some failures never reach your handler at all, because the SDK retries them first.
Error types
| HTTP Status | Error Code | Python | TypeScript | Go |
|---|---|---|---|---|
| 400 | BAD_REQUEST | BadRequestError | BadRequestError | *BadRequestError |
| 401 | AUTHENTICATION_ERROR | AuthenticationError | AuthenticationError | *UnauthorizedError |
| 403 | AUTHORIZATION_ERROR | AuthorizationError | AuthorizationError | *ForbiddenError |
| 404 | NOT_FOUND | NotFoundError | NotFoundError | *NotFoundError |
| 409 | CONFLICT | ConflictError | ConflictError | *ConflictError |
| 422 | VALIDATION_ERROR | ValidationError | ValidationError | *UnprocessableEntityError |
| 429 | RATE_LIMIT_EXCEEDED | RateLimitError | RateLimitError | *TooManyRequestsError |
| 500 | INTERNAL_SERVER_ERROR | InternalServerError | InternalServerError | *InternalServerError |
Each SDK's base class:
- Python:
LevelFourError - TypeScript:
LevelFourError - Go:
*core.APIError(useerrors.Asto match specific types)
Python and TypeScript add two more, for failures that carry no HTTP status:
- Python:
LevelFourConnectionError,LevelFourTimeoutError - TypeScript:
LevelFourConnectionError,LevelFourTimeoutError
status_code is 0 on both, so a branch keyed on the status code will not catch a connection failure or a timeout. Catch the class by name.Catching errors
from levelfour import (
LevelFour,
LevelFourError,
AuthenticationError,
NotFoundError,
RateLimitError,
ValidationError,
)
client = LevelFour()
try:
detail = client.recommendations.get("rec_123")
except NotFoundError as e:
print(f"Not found: {e.message}")
print(f"Status: {e.status_code}")
print(f"Code: {e.code}")
except RateLimitError as e:
print(f"Rate limited. Retry after: {e.retry_after}")
except ValidationError as e:
print(f"Validation errors: {e.details}")
except AuthenticationError as e:
print(f"Auth failed: {e.message}")
except LevelFourError as e:
print(f"API error {e.status_code}: {e.message}")LevelFourError matches every class in the table above, so a specific branch below it never runs and you never read the fields that class adds.Error properties
All exceptions inherit from LevelFourError:
| Property | Type | Description |
|---|---|---|
status_code | int | HTTP status code (0 for connection/timeout errors) |
code | str | Error code string |
message | str | Human-readable error message |
details | dict | list | None | Additional error context |
RateLimitError adds:
| Property | Type | Description |
|---|---|---|
retry_after | str | None | Seconds until retry is allowed |
All error classes extend LevelFourError:
| Property | Type | Description |
|---|---|---|
statusCode | number | undefined | HTTP status code |
body | unknown | Parsed error response body |
rawResponse | RawResponse | undefined | Raw HTTP response |
message | string | Error message |
All error types embed *core.APIError:
| Property | Type | Description |
|---|---|---|
StatusCode | int | HTTP status code |
Body | typed | Parsed error response (type varies per error) |
The Body field is typed per error: BadRequest, AuthenticationError, AuthorizationError, NotFound, Conflict, *HTTPValidationError, RateLimitError, InternalServer.
Retry behavior
Each SDK retries a failed request on its own, with exponential backoff. The default is two retries.
Retryable errors
| Condition | Retried |
|---|---|
| Network errors / timeouts | Yes |
| 408 Request Timeout | Yes |
| 409 Conflict | Yes |
| 429 Too Many Requests | Yes |
| 5xx Server Errors | Yes |
| 400 Bad Request | No |
| 401 Unauthorized | No |
| 403 Forbidden | No |
| 404 Not Found | No |
| 422 Validation Error | No |
Configuring retries
# five retries
client = LevelFour(max_retries=5)
# no retries at all
client = LevelFour(max_retries=0)Per-request override
summary = client.recommendations.get_savings_by_provider(
request_options={"max_retries": 5},
)Retries are one of several per-request options. Each SDK page has the full set: Python, TypeScript, Go.
Rate limits
Each API key can make 600 requests a minute. Past that, the API answers 429 with the code RATE_LIMIT_EXCEEDED, and the SDKs back off and retry it as the retryable errors table shows. A batch job that walks many pages stays under the limit by paging sequentially rather than in parallel.
Idempotent writes
Send an Idempotency-Key header on a POST, PATCH or PUT to make retrying it safe.
- The key must be a version 4 UUID. Anything else is a
422. - A successful response is kept for 24 hours. The same key from the same API key inside that window gets the stored response back, with an
Idempotent-Replayed: trueheader, and the write does not run again. - A failed request,
4xxor5xx, is not kept, so retrying it with the same key runs it. - A request whose key is still in flight gets
409. Wait for the first one to finish. - Keys belong to the caller that sent them. Two API keys using the same value do not collide.
curl -s -X POST "https://api.levelfour.ai/api/v1/webhooks/endpoints" \
-H "Authorization: Bearer $LEVELFOUR_API_KEY" \
-H "Idempotency-Key: $(uuidgen | tr 'A-Z' 'a-z')" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com/webhook", "event_types": ["optimization.completed"]}'Generate a new key for each distinct write, and reuse it only when retrying that same write.
Next
- SDKs compares the three clients and their constructor options, including the retry default
- API Reference is the JSON envelope these error codes arrive in over REST
- Pagination is the other cross-SDK behavior, with auto-paginating iterators
Webhooks
What LevelFour posts to your endpoint when a recommendation or an optimization changes state, and how to verify the signature before you act on it.
Pagination
The pagination envelope every list response carries, and how to walk it: auto-iterate, one page at a time, collect everything, or by hand.