Introduce a machine-readable, domain-level error taxonomy in a new apify.errors module. This is a low-risk, additive change with high leverage for machine-driven (agent) use.
It builds on apify-client-python#423: the client already exposes typed, HTTP-status-based exceptions (ApifyApiError + UnauthorizedError, ForbiddenError, NotFoundError, RateLimitError, ServerError, …). This issue adds the next layer up — Actor-run-aware errors with a .retryable flag — and maps the client's exceptions into it at the SDK boundary.
Problem
The SDK itself defines zero custom exceptions. Failures surface as generic builtins (RuntimeError, ValueError) or as raw apify_client errors. An autonomous agent can't branch on those to decide retry vs. reformulate input vs. pick another tool vs. give up. That needs typed, coded, .retryable-tagged errors with Actor-run semantics (a 429 is transient; a FAILED run with a bad input is not).
Proposed design
ApifyError(Exception) with code: str and retryable: bool, plus typed subclasses:
class ApifyError(Exception):
code: str
retryable: bool
class ActorRunError(ApifyError):
def __init__(self, run: Run): ... # .run_id .status .exit_code .status_message
class InputValidationError(ApifyError, ValueError): ... # still caught by `except ValueError`
class ChargeLimitExceededError(ApifyError): ...
class RateLimitError(ApifyError): retryable = True
class AuthenticationError(ApifyError): ...
class ActorTimeoutError(ApifyError): retryable = True
Export the names additively from apify/__init__.py.
Mapping apify_client → apify.errors
Translate the client's exceptions at the SDK boundary (the client's RateLimitError / UnauthorizedError names overlap, so the mapping must be explicit):
apify_client.errors |
apify.errors |
UnauthorizedError (401), ForbiddenError (403) |
AuthenticationError |
RateLimitError (429) |
RateLimitError (retryable=True) |
ServerError (5xx) |
ApifyError (retryable=True) |
InvalidRequestError (400) |
InputValidationError / ApifyError |
other ApifyApiError |
ApifyError |
ActorRunError, ChargeLimitExceededError, and ActorTimeoutError are derived from run status / exit code, not from HTTP errors.
Acceptance criteria
Risk: Medium. New exceptions appear only on new code paths (opt-in raise_on_failure); existing methods keep raising RuntimeError / ValueError.
Introduce a machine-readable, domain-level error taxonomy in a new
apify.errorsmodule. This is a low-risk, additive change with high leverage for machine-driven (agent) use.It builds on apify-client-python#423: the client already exposes typed, HTTP-status-based exceptions (
ApifyApiError+UnauthorizedError,ForbiddenError,NotFoundError,RateLimitError,ServerError, …). This issue adds the next layer up — Actor-run-aware errors with a.retryableflag — and maps the client's exceptions into it at the SDK boundary.Problem
The SDK itself defines zero custom exceptions. Failures surface as generic builtins (
RuntimeError,ValueError) or as rawapify_clienterrors. An autonomous agent can't branch on those to decide retry vs. reformulate input vs. pick another tool vs. give up. That needs typed, coded,.retryable-tagged errors with Actor-run semantics (a 429 is transient; a FAILED run with a bad input is not).Proposed design
ApifyError(Exception)withcode: strandretryable: bool, plus typed subclasses:Export the names additively from
apify/__init__.py.Mapping
apify_client→apify.errorsTranslate the client's exceptions at the SDK boundary (the client's
RateLimitError/UnauthorizedErrornames overlap, so the mapping must be explicit):apify_client.errorsapify.errorsUnauthorizedError(401),ForbiddenError(403)AuthenticationErrorRateLimitError(429)RateLimitError(retryable=True)ServerError(5xx)ApifyError(retryable=True)InvalidRequestError(400)InputValidationError/ApifyErrorApifyApiErrorApifyErrorActorRunError,ChargeLimitExceededError, andActorTimeoutErrorare derived from run status / exit code, not from HTTP errors.Acceptance criteria
apify.errorsmodule withApifyError(code, retryable)and the subclasses above.apify_clientexceptions mapped to the appropriate typed error at the SDK boundary.apify/__init__.py.except ValueErrorstill catchesInputValidationError.Risk: Medium. New exceptions appear only on new code paths (opt-in
raise_on_failure); existing methods keep raisingRuntimeError/ValueError.