Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
20016ec
chore: sync Rust client with Apify OpenAPI spec v2-2026-07-10T105921Z
claude Jul 10, 2026
2c5e045
feat: add lazy pagination iterators to all collection clients
claude Jul 10, 2026
c8efe6e
test: add hermetic ListIterator unit tests; single-page env-var iterator
claude Jul 10, 2026
84a9f3d
docs: clarify iterate() limit semantics; drop process reference in ba…
claude Jul 10, 2026
28b49d1
fix: align iterate() limit with reference (total cap) and stop short-…
claude Jul 10, 2026
4d84c41
test: force multi-page fetching in iterate_store via with_chunk_size
claude Jul 10, 2026
44b8f70
docs: correct iterate_items/pagination docs and add cap-truncation test
claude Jul 10, 2026
d61a7fe
docs: clarify iterate() limit is total cap; harden missing-total head…
claude Jul 10, 2026
0edc585
docs: add iterate() total-cap note and list_iterator! macro to task_c…
claude Jul 10, 2026
df02875
feat: add KVS iterate_keys, hermetic iterator tests, reconcile iterat…
claude Jul 10, 2026
d6809f4
fix: normalize/clamp iterate_keys page limit, re-export cursor iterat…
claude Jul 10, 2026
fd51514
docs: make iterate_keys example self-contained and avoid Option debug…
claude Jul 10, 2026
bc4e40a
fix: iterate_keys terminates on isTruncated; drop QueryParams from do…
claude Jul 10, 2026
4996e06
fix: remove out-of-scope KVS get_records endpoint
claude Jul 10, 2026
d4d1221
docs: document set_status_message and get_openapi_definition; simplif…
claude Jul 10, 2026
c1e3757
docs: clarify iterate_items filter edge cases, trim comment, sync ite…
claude Jul 10, 2026
255235e
docs: complete ActorStartOptions fields, JSONL format, robust store e…
claude Jul 10, 2026
44fb51d
docs: link task start/call rows to ActorStartOptions field table
claude Jul 10, 2026
d0bebf2
fix: retry create-then-iterate tests for collection list eventual con…
claude Jul 10, 2026
96e6032
test: widen eventual-consistency retry budget and trim helper docstring
claude Jul 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,57 @@ All notable changes to the Rust Apify API client are documented here. The format
based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project adheres
to [Semantic Versioning](https://semver.org/).

## [0.6.0] - 2026-07-10

### Added
- Lazy async pagination iterators on every collection client, via a shared generic
`ListIterator<T>` (exported at the crate root). New `iterate()` methods on the actor, actor
version, environment-variable, build, run, dataset, key-value-store, request-queue, schedule,
task, webhook, and webhook-dispatch collection clients, plus `DatasetClient::iterate_items()`
for dataset items. Each yields one item at a time, fetching pages on demand — the idiomatic
counterpart to the reference client's async-iterable list results. The options' `limit` caps
the total number of items yielded (matching the reference client), and `ListIterator::with_chunk_size`
sets the per-request page size.
- `KeyValueStoreClient::iterate_keys()`, returning a cursor-based `KeyValueStoreKeysIterator`
that auto-paginates a store's keys via `exclusiveStartKey`/`nextExclusiveStartKey` (the
auto-paginating counterpart to `list_keys`, matching the reference client's `listKeys()`
async-iterable).
- Re-exported `StoreActorIterator` at the crate root.

### Changed
- Bumped `API_SPEC_VERSION` to `v2-2026-07-10T105921Z`. The spec delta (added `401`/`402`
error responses and relaxed field nullability/optionality) needs no code change: error
responses are handled generically and response models are forward-compatible.
- `StoreCollectionClient::iterate` now uses the shared `ListIterator`, and `StoreActorIterator`
is a type alias for `ListIterator<ActorStoreListItem>`. As part of this, `store().iterate()`'s
`options.limit` changed from a per-page size (0.5.0) to a cap on the total number of items
yielded, for consistency with the reference client and the other `iterate()` methods; set the
per-page size with `ListIterator::with_chunk_size` instead. The `StoreActorIterator` type alias
itself is unchanged.
- Corrected the `src/models.rs` module doc to describe forward-compatibility accurately.
- Documented `ApifyClient::set_status_message` and `BuildClient::get_openapi_definition` in the
`docs/` pages and README.
- Simplified the total-cap truncation in the offset and key-value-store iterators (removed a
dead `.max(0)` clamp on an already-positive remaining count).
- Bumped crate version to `0.6.0`.

### Removed
- `KeyValueStoreClient::get_records` and `GetRecordsOptions`. The `GET /v2/key-value-stores/{storeId}/records`
endpoint is not implemented by the reference JS client, so it is out of scope; its removal
corrects an earlier scope violation.

### Documentation
- Documented all `ActorStartOptions` fields in `docs/actors.md` (added the previously undocumented
`restart_on_error`, `force_permission_level`, and `webhooks`), and linked the task `start`/`call`
rows in `docs/tasks.md` to that field table.
- Listed `JSONL` in the `download_items` format summary in `docs/storages.md` for consistency with
the `DownloadItemsFormat` variant list.
- Noted why the request-queue iterator is named `paginate_requests` (mirrors the reference JS
`paginateRequests`) rather than an `iterate_*` verb.
- Made the `run_store_actor` example resilient to Store ranking shifts by falling back to the
well-known `apify/hello-world` identifier, and cleaned up `Option` output in the `get_account`
and `iterate_store` examples and the `docs/README.md` quick-start snippet.

## [0.5.0] - 2026-07-10

### Added
Expand Down
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "apify-client"
version = "0.5.0"
version = "0.6.0"
authors = ["Apify Technologies <support@apify.com>"]
description = "An official, but experimental, AI-generated and AI-maintained Rust client for the Apify API (https://apify.com)."
license = "Apache-2.0"
Expand Down Expand Up @@ -28,7 +28,7 @@ brotli = "7"
flate2 = "1"

[dev-dependencies]
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
tokio = { version = "1", features = ["macros", "rt-multi-thread", "time"] }
uuid = { version = "1", features = ["v4"] }

[features]
Expand Down
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,15 @@ It provides a resource-oriented, async interface that mirrors the official
- Resource clients for Actors, runs, builds, tasks, datasets, key-value stores, request
queues, schedules, webhooks, the Apify Store, users and logs.
- Convenience helpers: run/wait, log streaming (redirection; needs the `futures-util` crate —
see [Installation](#installation)), lazy Store iteration.
see [Installation](#installation)), lazy Store iteration, and `set_status_message` for
updating the current Actor run's status (see [`docs/README.md`](docs/README.md#convenience-methods)).
- A replaceable HTTP transport for testing or custom runtimes.

## Installation

```toml
[dependencies]
apify-client = "0.5"
apify-client = "0.6"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
serde_json = "1" # for the `serde_json::Value` responses used in the Quick start
```
Expand All @@ -36,7 +37,7 @@ project needs `serde_json`. Two more dependencies are needed only for specific f
[`raw_log`](examples/raw_log.rs) examples import `futures_util::StreamExt` for this. See
[`docs/misc.md`](docs/misc.md#logs--clientlogbuild_or_run_id).
- `chrono = "0.4"` — only if you construct or read timestamp values yourself. Model timestamp
fields (e.g. `Actor::created_at`, `ActorRun::started_at`) are typed as `chrono::DateTime<Utc>`
fields (e.g. `Actor::created_at`, `ActorRun::started_at`) are typed as `Option<chrono::DateTime<Utc>>`
and `chrono` is **not** re-exported, so snippets that call `chrono::Utc::now()` (e.g. the
`monthly_usage` example in [`docs/misc.md`](docs/misc.md#users--clientme--clientuserid) and the
account example) need it as a direct dependency.
Expand All @@ -49,7 +50,7 @@ project needs `serde_json`. Two more dependencies are needed only for specific f
By default the client uses the system TLS (`native-tls`). To use rustls instead:

```toml
apify-client = { version = "0.5", default-features = false, features = ["rustls"] }
apify-client = { version = "0.6", default-features = false, features = ["rustls"] }
```

## Quick start
Expand Down
70 changes: 65 additions & 5 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ Add the crate and an async runtime:

```toml
[dependencies]
apify-client = "0.5"
apify-client = "0.6"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
```

Expand All @@ -56,7 +56,7 @@ use apify_client::ApifyClient;
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = ApifyClient::new("my-api-token");
let user = client.me().get().await?.expect("account");
println!("Logged in as {:?}", user.username);
println!("Logged in as {}", user.username.as_deref().unwrap_or("(none)"));
Ok(())
}
```
Expand All @@ -78,15 +78,19 @@ types is:
- Actors: `ActorStartOptions`, `ActorBuildOptions`, `ActorListOptions`
- Runs: `RunListOptions`, `RunResurrectOptions`, `RunMetamorphOptions`, `RunChargeOptions`, `LastRunOptions`
- Datasets: `DatasetListItemsOptions`, `DatasetDownloadOptions`, `DownloadItemsFormat`
- Key-value stores: `ListKeysOptions`, `GetRecordsOptions`, `GetRecordOptions`
- Key-value stores: `ListKeysOptions`, `GetRecordOptions`
- Request queues: `ListRequestsOptions`
- Store: `StoreListOptions`
- Logs: `LogOptions`
- Shared: `ListOptions`, `StorageListOptions`
- Client configuration: `RequestCompression`

plus the common container `PaginationList` and the query helper `QueryParams`. Import any of them
directly from `apify_client`:
plus the common container `PaginationList`, `ListIterator`
(the return type of every collection client's `iterate()` method), `StoreActorIterator`
(a type alias for `ListIterator<ActorStoreListItem>`, the return type of
`StoreCollectionClient::iterate`), and the two cursor-based iterators `KeyValueStoreKeysIterator`
(from `KeyValueStoreClient::iterate_keys`) and `RequestQueueRequestsIterator` (from
`RequestQueueClient::paginate_requests`). Import any of them directly from `apify_client`:

```rust,no_run
use apify_client::{ApifyClient, ActorListOptions, StoreListOptions, DownloadItemsFormat};
Expand Down Expand Up @@ -167,6 +171,32 @@ reach those values the client maps Rust's native `std::env::consts::OS` spelling
(`macos` → `darwin`, `windows` → `win32`, `solaris`/`illumos` → `sunos`); all other tokens
(`linux`, `android`, `freebsd`, …) are already identical and pass through unchanged.

### Convenience methods

Beyond the resource accessors, `ApifyClient` exposes one convenience method:

| Method | Arguments | Returns | Description |
|---|---|---|---|
| `set_status_message(message, is_terminal)` | `message: &str`, `is_terminal: bool` | `ActorRun` | Sets the status message of the *current* Actor run. |

`set_status_message` updates the run identified by the `ACTOR_RUN_ID` environment variable, so it
only works when called from inside an Actor run. `message` is the human-readable status text; when
`is_terminal` is `true` the message becomes final and is not overwritten by later updates. It
returns the updated [`ActorRun`](runs.md), or
[`ApifyClientError::InvalidArgument`](#error-handling) if `ACTOR_RUN_ID` is not set.

```rust,no_run
use apify_client::ApifyClient;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = ApifyClient::new("my-api-token");
// Called from inside an Actor run (reads ACTOR_RUN_ID from the environment).
client.set_status_message("Processing input…", false).await?;
Ok(())
}
```

## Resource clients

Accessor methods on `ApifyClient` return resource clients — the collection accessor (plural)
Expand All @@ -180,6 +210,36 @@ lists/creates, and the single-resource accessor (singular) operates on one resou
Each resource has a dedicated page, linked under **Resource clients** in the [Contents](#contents)
above (Actors, runs, builds, tasks, storages, schedules, webhooks, and store/users/logs).

### Iterating collections

A collection's `list(...)` method returns a single `PaginationList` page. To walk every item
across all pages without tracking offsets yourself, call `iterate(...)` instead: it returns a
lazy `ListIterator` (re-exported at the crate root) that fetches the next page from the API on
demand as you consume items. Every collection client provides it (`actors`, `builds`, `runs`,
`tasks`, `datasets`, `key_value_stores`, `request_queues`, `schedules`, `webhooks`,
`webhook_dispatches`, `store`, and the nested Actor `versions`/`env_vars`). `DatasetClient`
exposes `iterate_items()` for dataset items, and `KeyValueStoreClient` exposes `iterate_keys()`
for store keys (cursor-based). The options' `limit` caps the total number of items yielded (unset
iterates everything); to control the per-request page size, call `.with_chunk_size(n)` on the
returned iterator (offset-paginated iterators only).

```rust,no_run
use apify_client::{ApifyClient, ActorListOptions};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = ApifyClient::new("my-api-token");
let mut actors = client.actors().iterate(ActorListOptions {
my: Some(true),
..Default::default()
});
while let Some(actor) = actors.next().await? {
println!("{}", actor.id);
}
Ok(())
}
```

## Error handling

Every fallible method returns `Result<T, ApifyClientError>`. The variants are:
Expand Down
25 changes: 19 additions & 6 deletions docs/actors.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ be an Actor ID or a `username~name` (or `username/name`) reference.
| Method | Arguments | Returns | Description |
|---|---|---|---|
| `list(options)` | `ActorListOptions { offset, limit, desc, my, sort_by }` | `PaginationList<Actor>` | Lists your Actors. |
| `iterate(options)` | `ActorListOptions` | `ListIterator<Actor>` | Lazily iterates all Actors across pages (auto-pagination). |
| `create(actor)` | `&impl Serialize` | `Actor` | Creates an Actor from a definition. |

## `ActorClient`
Expand All @@ -32,10 +33,22 @@ be an Actor ID or a `username~name` (or `username/name`) reference.

### `ActorStartOptions`

`build`, `memory_mbytes`, `timeout_secs`, `wait_for_finish`, `max_items`,
`max_total_charge_usd`, `content_type` — all optional. Used by both `start` and `call`
(for `call`, `wait_for_finish` is server-side; the `wait_secs` argument controls
client-side polling).
All fields are optional. Used by both `start` and `call` here, and by the identical `start` /
`call` methods on [tasks](tasks.md) (for `call`, `wait_for_finish` is server-side; the
`wait_secs` argument controls client-side polling).

| Field | Type | Description |
|---|---|---|
| `build` | `Option<String>` | Tag or number of the build to run (e.g. `latest`, `0.1.2`). |
| `memory_mbytes` | `Option<i64>` | Memory in megabytes allocated for the run. |
| `timeout_secs` | `Option<i64>` | Timeout for the run in seconds (`0` means no timeout). |
| `wait_for_finish` | `Option<i64>` | Maximum seconds to wait server-side for the run to finish (max 60). |
| `max_items` | `Option<i64>` | Maximum number of dataset items to charge (pay-per-result Actors). |
| `max_total_charge_usd` | `Option<f64>` | Maximum total charge in USD (pay-per-event Actors). |
| `content_type` | `Option<String>` | Content type of the input body. Defaults to `application/json`. |
| `restart_on_error` | `Option<bool>` | Whether to restart the run if it fails. |
| `force_permission_level` | `Option<String>` | Override the Actor's permission level for this run. |
| `webhooks` | `Option<Vec<serde_json::Value>>` | Ad-hoc webhooks to attach to this run. Encoded as base64 JSON in the `webhooks` query parameter, matching the reference clients. |

The `wait_secs` argument of `call` (and of `wait_for_finish` on runs/builds) controls the
client-side polling budget:
Expand Down Expand Up @@ -156,6 +169,6 @@ println!("build {} status {:?}", build.id, build.status);
## Actor versions and environment variables

`ActorVersionClient`: `get`, `update`, `delete`, `env_var(name)`, `env_vars()`.
`ActorVersionCollectionClient`: `list(options)`, `create(version)`.
`ActorVersionCollectionClient`: `list(options)`, `iterate(options)`, `create(version)`.
`ActorEnvVarClient`: `get`, `update`, `delete`.
`ActorEnvVarCollectionClient`: `list()`, `create(env_var)`.
`ActorEnvVarCollectionClient`: `list()`, `iterate()`, `create(env_var)`.
2 changes: 2 additions & 0 deletions docs/builds.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ collections are available via `actor.builds()`.
| Method | Arguments | Returns | Description |
|---|---|---|---|
| `list(options)` | `ListOptions` | `PaginationList<Build>` | Lists builds. |
| `iterate(options)` | `ListOptions` | `ListIterator<Build>` | Lazily iterates all builds across pages (auto-pagination). |

## `BuildClient`

Expand All @@ -17,6 +18,7 @@ collections are available via `actor.builds()`.
| `abort()` | — | `Build` | Aborts the build. |
| `delete()` | — | `()` | Deletes the build. |
| `wait_for_finish(wait_secs)` | `Option<i64>` | `Build` | Polls until the build is terminal. |
| `get_openapi_definition()` | — | `Option<serde_json::Value>` | Fetches the OpenAPI definition generated for the build (raw JSON, endpoint `.../openapi.json`). |
| `log()` | — | `LogClient` | Access the build's log. |

The returned `Build` model's fields (`id`, `status`, `build_number`, …) are documented in
Expand Down
24 changes: 22 additions & 2 deletions docs/misc.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,12 @@
| `iterate(options)` | `StoreListOptions` | `StoreActorIterator` | Lazy, page-fetching iterator. |

`StoreListOptions`: `offset`, `limit`, `search`, `sort_by`, `category`, `username`,
`pricing_model`.
`pricing_model`. `limit` means a single page's size for `list`, but a cap on the *total* number of
items yielded for `iterate` (see below).

`StoreActorIterator::next()` is `async` and fallible — it returns
`StoreActorIterator` is a type alias for `ListIterator<ActorStoreListItem>` (the shared iterator
returned by every collection's `iterate`), re-exported at the crate root alongside `ListIterator`
itself. Its `next()` is `async` and fallible — it returns
`ApifyClientResult<Option<ActorStoreListItem>>` (i.e. `Result<Option<ActorStoreListItem>, ApifyClientError>`),
fetching the next page on demand and yielding `Ok(None)` once the listing is exhausted. Drive it
with `.await?`:
Expand All @@ -29,6 +32,23 @@ while let Some(actor) = iter.next().await? {
# }
```

`options.limit` caps the total number of Actors the iterator yields (unset iterates the whole
Store). The per-request page size is separate: call `.with_chunk_size(n)` on the returned
`StoreActorIterator` to fetch `n` Actors per API call (when unset, the API's default page size is
used). If you set a large `limit` cap, also set `with_chunk_size` so the first request does not ask
for the entire cap at once — for example, `client.store().iterate(opts).with_chunk_size(50)`:

```rust,no_run
# use apify_client::{ApifyClient, StoreListOptions};
# async fn run(client: ApifyClient) -> Result<(), Box<dyn std::error::Error>> {
let mut iter = client.store().iterate(StoreListOptions::default()).with_chunk_size(50);
while let Some(actor) = iter.next().await? {
println!("{}", actor.id);
}
# Ok(())
# }
```

`ActorStoreListItem` (from `apify_client::models`) is the element type yielded by both `list`
and the iterator. Its fields:

Expand Down
1 change: 1 addition & 0 deletions docs/runs.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ collections are available via `actor.runs()` and `task.runs()`.
| Method | Arguments | Returns | Description |
|---|---|---|---|
| `list(options, filter)` | `ListOptions`, `RunListOptions { status, started_after, started_before }` | `PaginationList<ActorRun>` | Lists runs, optionally filtered by status and start time. |
| `iterate(options, filter)` | `ListOptions`, `RunListOptions` | `ListIterator<ActorRun>` | Lazily iterates all runs across pages (auto-pagination). |

## `RunClient`

Expand Down
1 change: 1 addition & 0 deletions docs/schedules.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Obtained via `client.schedules()` (collection) and `client.schedule(id)` (single
| Method | Arguments | Returns | Description |
|---|---|---|---|
| `list(options)` | `ListOptions` | `PaginationList<Schedule>` | Lists schedules. |
| `iterate(options)` | `ListOptions` | `ListIterator<Schedule>` | Lazily iterates all schedules across pages (auto-pagination). |
| `create(schedule)` | `&impl Serialize` | `Schedule` | Creates a schedule. |

## `ScheduleClient`
Expand Down
Loading
Loading