Skip to main content
Version: Next

Upgrading to v3

This guide lists the breaking changes between Apify Python API Client v2.x and v3.0.

Python 3.11+ required

Support for Python 3.10 has been dropped. The Apify Python API Client v3.x now requires Python 3.11 or later — make sure your environment is on a compatible version before upgrading.

Typed responses

Methods now return Pydantic models instead of dicts. Access fields with their snake_case attribute names.

run = client.actor('apify/hello-world').call(run_input={'key': 'value'})

# Before
dataset_id = run['defaultDatasetId']

# After
dataset_id = run.default_dataset_id

Two endpoints still return plain types because their payloads are user-defined: DatasetClient.list_items() returns DatasetItemsPage (with items: list[dict[str, Any]] following the Actor output schema), and KeyValueStoreClient.get_record() returns a dict.

On the input side, methods now also accept Pydantic models in addition to dicts. Plain dicts continue to work — keys may use either snake_case or camelCase, and each input shape has a matching TypedDict so editors and type checkers can validate the keys. See Typed models for details.

Tiered timeouts

The single global timeout has been replaced by four tiers — short (5 s), medium (30 s), long (360 s), and no_timeout. Each method picks an appropriate default. Override per call, or change tier defaults on the constructor:

from datetime import timedelta

client = ApifyClient(token='MY-APIFY-TOKEN', timeout_short=timedelta(seconds=10))

# Per-call override.
client.actor('apify/hello-world').get(timeout='long')
client.actor('apify/hello-world').get(timeout=timedelta(seconds=120))

If your code relied on the previous global timeout, review the methods you use. See Timeouts for the full reference.

404 raises NotFoundError on ambiguous endpoints

Direct .get(id) and .delete(id) calls still swallow 404 into None. But where a 404 could mean either the parent or the sub-resource is missing, the client now raises NotFoundError instead of returning None.

from apify_client.errors import NotFoundError

# Before — returned None on 404.
dataset = client.run('some-run-id').dataset().get()

# After — raises NotFoundError; handle it explicitly.
try:
dataset = client.run('some-run-id').dataset().get()
except NotFoundError:
dataset = None

Affected paths:

Status-specific subclasses such as RateLimitError are now available too — see Error handling. Existing except ApifyApiError handlers are unaffected.

Keyword-only arguments

Secondary parameters on these methods can no longer be passed positionally.

# Before
client.key_value_store('my-store').set_record('my-key', {'data': 1}, 'application/json')
client.run('my-run').charge('my-event', 5, 'my-idempotency-key')

# After
client.key_value_store('my-store').set_record('my-key', {'data': 1}, content_type='application/json')
client.run('my-run').charge('my-event', count=5, idempotency_key='my-idempotency-key')

Affected signatures:

Literal string aliases instead of StrEnum classes

Generated enum-like types are now Literal string aliases instead of StrEnum classes. Pass plain strings instead of enum members; type checkers infer the allowed set directly from each method signature, so nothing needs to be imported for argument validation.

# Before
event_types=[WebhookEventType.ACTOR_RUN_SUCCEEDED]

# After
event_types=['ACTOR.RUN.SUCCEEDED']

Affected types: ActorJobStatus, ActorPermissionLevel, ErrorType, GeneralAccess, HttpMethod, RunOrigin, SourceCodeFileFormat, StorageOwnership, VersionSourceType, WebhookDispatchStatus, WebhookEventType. For more on the generated types and how they fit into the typed client surface, see Typed models.

Async iterate_* are no longer coroutine functions

DatasetClientAsync.iterate_items() and KeyValueStoreClientAsync.iterate_keys() are now plain def functions returning AsyncIterator[T]. Consumer code (async for ...) is unchanged. If you annotate the call's return value, change AsyncGenerator[T, None] to AsyncIterator[T]. For background on these helpers and how they fit alongside page models, see Pagination.

Removed deprecated APIs

Pluggable HTTP client

Additive change, non-breaking. The HTTP layer is now abstracted behind the HttpClient and HttpClientAsync base classes, so you can swap the underlying transport for your own implementation. The default client built on Impit is unchanged and existing code keeps working out of the box. To plug in a custom client, pass an instance to ApifyClient.with_custom_http_client() — see Custom HTTP clients for the full walkthrough.