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:
- Chained calls that target a default sub-resource without an ID —
run.dataset(),run.key_value_store(),run.request_queue(),run.log(). - Chained
LogClientreads —run.log().get(),.get_as_bytes(),.stream(). - Singleton endpoints fetched via a fixed path —
ScheduleClient.get_log(),TaskClient.get_input(),DatasetClient.get_statistics(),UserClient.monthly_usage(),UserClient.limits(),WebhookClient.test(). Their return types changed fromT | NonetoT.
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:
KeyValueStoreClient—get_record,get_record_as_bytes,stream_record,set_record.RunClient—charge,get_status_message_watcher.ApifyApiErrorconstructor —methodis keyword-only.
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
DatasetClient.download_items()→ useDatasetClient.get_items_as_bytes()(identical signature).max_unprocessed_requests_retriesandmin_delay_between_unprocessed_requests_retriesonRequestQueueClient.batch_add_requests()— were no-ops; drop them.exclusive_start_idonRequestQueueClient.list_requests()→ usecursorwithnext_cursorfrom the previous response, oriterate_requests().
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.