Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion .github/workflows/php-integration-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ jobs:
uses: shivammathur/setup-php@v2
with:
php-version: '8.1'
extensions: mbstring, json, curl, zlib
# intl is optional for users but loaded here so the punycode host normalization test runs.
extensions: mbstring, json, curl, zlib, intl
coverage: none
tools: composer:v2

Expand Down
41 changes: 41 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,46 @@
# Changelog

## 0.12.0

- Synced with Apify OpenAPI spec `v2-2026-11-01T214220Z`. Response models return the new fields as
received (`toArray()`/`get()`), including the `RunStandby` deployment of Server Actor runs and a
`null` schedule `uiState`.
- Added a `deployment` filter to `RunListOptions` (Actor Standby deployments; honoured by `runs()` and
`actor($id)->runs()`) and an `includeInputSchema` option to `StoreListOptions`.
- `UserClient::monthlyUsage()`, `limits()` and `updateLimits()` now work for any user ID, not only `me()`,
as the spec documents the by-ID endpoints; they no longer throw `LogicException`.
- Added `RunClient::iterateDatasetItems()` with `RunIterateDatasetItemsOptions`: lazily reads a run's
default dataset while the run is still producing items and returns once the run has finished and the
dataset is drained.
- **Breaking:** the `short` and `medium` timeout tiers now default to 5 and 30 seconds, as in the
reference client, instead of the overall `timeoutSecs`. The `long` tier and `timeoutMaxSecs` still
default to `timeoutSecs` (360 seconds). Configure `timeoutShortSecs`/`timeoutMediumSecs` or pass a
per-call `$timeoutSecs` for a call that needs longer.
- A request that asks the API to hold its response with `waitForFinish` (`RunClient::get()`,
`BuildClient::get()`, `ActorClient::defaultBuild()` and the `waitForFinish` option of `ActorClient::start()`/
`build()` and `TaskClient::start()`) now gets that hold, at most 60 seconds, on top of its tier timeout.
- **Breaking:** the URL fields of response models that the spec declares as `format: uri` are returned in the
WHATWG URL serialization (trailing slash for an empty path, lowercased scheme and host, default port
dropped, punycode host with the optional `intl` extension, percent-encoded unsafe characters), e.g.
`ActorRun::getContainerUrl()` returns `https://abc.runs.apify.net/`.
- **Breaking:** `ScheduleClient::getLog()` returns the list of invocation entries (`message`, `level`,
`createdAt`) the endpoint responds with, instead of the raw JSON text, and uses the `medium` tier.
- **Breaking:** `DatasetClient::createItemsPublicUrl()` and `KeyValueStoreClient::createKeysPublicUrl()` throw
`InvalidArgumentException` for an `expiresInSecs` of zero or less before sending any request; leave it out
for a URL that never expires. `createItemsPublicUrl()`, `createKeysPublicUrl()` and `getRecordPublicUrl()`
take a trailing `$timeoutSecs` for the request that fetches the storage (default `long` tier).
- `DatasetClient::pushItems()` no longer retries a request that timed out, since the API may already have stored
the items.
- Aligned the default timeout tier of individual methods with the reference client: `short` for
`ActorClient::validateInput()`, `BuildClient::abort()`, the Actor version and env-var collection calls, the
storage collections' `getOrCreate()`, the schedule and webhook `create()`, and the request-queue `listHead()`,
`addRequest()`, `batchDeleteRequests()` and `deleteRequestLock()`; `long` for the key-value store `update()`
and `recordExists()`; `medium` for `DatasetClient::pushItems()`.
- `RunClient::waitForFinish()`, `BuildClient::waitForFinish()`, and `call()` on Actors and tasks take an
optional `$timeoutSecs`; polls and the start request of `call()` have no request timeout by default.
- Documented that `KeyValueStoreClient::getRecord()` never parses a record and that `setRecord()` sends its
value as given, also under `application/json`.

## 0.11.1

- Synced with Apify OpenAPI spec `v2-2026-10-26T112451Z`. The spec now documents the full Actor
Expand Down
20 changes: 13 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,11 @@ $configured = new ApifyClient(
| `maxRetries` | `8` | Maximum retries for failed requests. |
| `minDelayBetweenRetriesMillis` | `500` | Minimum delay between retries (exponential backoff). |
| `maxDelayBetweenRetriesMillis` | `timeoutSecs × 1000` (360000) | Upper bound (milliseconds) on the growing inter-retry delay; defaults to the request timeout expressed in milliseconds. |
| `timeoutSecs` | `360` | Overall per-request timeout, and the default duration of every timeout tier below that is left unset. |
| `timeoutShortSecs` / `timeoutMediumSecs` / `timeoutLongSecs` | `timeoutSecs` | Duration (seconds) of the named timeout tier — see [Timeout tiers](#timeout-tiers). |
| `timeoutMaxSecs` | `timeoutSecs` | Upper bound on any single request's timeout, including a per-call override. |
| `timeoutSecs` | `360` | Overall per-request timeout: the default duration of the `long` tier and of `timeoutMaxSecs`. |
| `timeoutShortSecs` | `5` | Duration (seconds) of the `short` timeout tier — see [Timeout tiers](#timeout-tiers). |
| `timeoutMediumSecs` | `30` | Duration (seconds) of the `medium` timeout tier. |
| `timeoutLongSecs` | `timeoutSecs` | Duration (seconds) of the `long` timeout tier. |
| `timeoutMaxSecs` | `timeoutSecs` | Upper bound on any single request attempt's timeout, including a per-call override and the growth across retries. |
| `compression` | `null` (brotli, falling back to gzip) | Request-body compression: `'brotli'`, `'gzip'`, or a custom `HttpCompressorInterface` — see [HTTP compression](#http-compression). |
| `userAgentSuffix` | `null` | Custom suffix appended to the `User-Agent` header. |
| `httpClient` | Guzzle | The replaceable transport (`Apify\Client\Http\HttpClientInterface`). |
Expand All @@ -84,9 +86,12 @@ not thrown — `get()` returns `null` and `delete()` is treated as a successful

Every method that sends a request is assigned a timeout tier: `short` (metadata reads/writes, e.g.
`get()`/`update()`/`delete()`), `medium` (listing/batch/trigger calls), or `long`
(downloads/uploads/streaming). Each tier's duration defaults to the client's single `timeoutSecs`, so
a client constructed without the tier options behaves exactly as before they existed; set
`timeoutShortSecs`/`timeoutMediumSecs`/`timeoutLongSecs` to give a tier its own duration instead.
(downloads/uploads/streaming). The tiers last 5 seconds (`short`), 30 seconds (`medium`) and the client's
`timeoutSecs`, 360 seconds by default (`long`); set `timeoutShortSecs`/`timeoutMediumSecs`/
`timeoutLongSecs` to give a tier another duration. A call that asks the API to hold its response with a
`waitForFinish` value (`RunClient::get()`, `BuildClient::get()`, `ActorClient::defaultBuild()`, and the
`waitForFinish` option of `ActorClient::start()`/`build()` and `TaskClient::start()`) gets that hold,
at most 60 seconds, on top of its tier, so the client never aborts a request the API is still holding.

Every method that sends a request additionally takes an optional trailing `$timeoutSecs`, overriding
the tier for that one call — a number of seconds, a tier name (`'short'`/`'medium'`/`'long'`), or
Expand All @@ -99,7 +104,8 @@ $client->requestQueue('my-queue')->unlockRequests(timeoutSecs: 'noTimeout'); //
```

`timeoutMaxSecs` caps every tier and every per-call override alike (defaulting to `timeoutSecs`), so
raise it whenever a call legitimately needs longer than the default 360 seconds. A request-queue
raise it whenever a call legitimately needs longer than the default 360 seconds. A failed attempt is
retried with a timeout doubled up to that cap. A request-queue
client's own `timeoutSecs` (`requestQueue(id, options)`) caps every tier and per-call override of
calls made through that client specifically, on top of `timeoutMaxSecs`.

Expand Down
3 changes: 3 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@
"friendsofphp/php-cs-fixer": "^3.52",
"phpstan/phpstan": "^1.11"
},
"suggest": {
"ext-intl": "Converts an internationalized host of a URL field in a response to punycode, as the reference client does."
},
"autoload": {
"psr-4": {
"Apify\\Client\\": "src/"
Expand Down
4 changes: 2 additions & 2 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Every class is under the `Apify\Client\` PSR-4 root. Use these when writing `use
|---|---|---|
| `Apify\Client\` | The entry point and version constants. | `ApifyClient`, `Version` |
| `Apify\Client\Model\` | Response models returned by the API. | `RequestQueueRequest`, `ActorEnvVar`, `Dataset`, `ActorRun`, `PaginationList` |
| `Apify\Client\Options\` | Option objects (all the `*Options` classes) **and** enums. | e.g. `ActorListOptions`, `ActorStartOptions`, `TaskStartOptions`, `RunListOptions`, `RunResurrectOptions`, `StorageListOptions`, `StoreListOptions`, `DatasetListItemsOptions`, `ListKeysOptions`, `GetRecordOptions`, `ListRequestsOptions`, `BatchAddRequestsOptions`, `PaginateRequestsOptions`, `LogOptions`, `DownloadItemsFormat` — see [options reference](options.md) for the full list |
| `Apify\Client\Options\` | Option objects (all the `*Options` classes) **and** enums. | e.g. `ActorListOptions`, `ActorStartOptions`, `TaskStartOptions`, `RunListOptions`, `RunIterateDatasetItemsOptions`, `RunResurrectOptions`, `StorageListOptions`, `StoreListOptions`, `DatasetListItemsOptions`, `ListKeysOptions`, `GetRecordOptions`, `ListRequestsOptions`, `BatchAddRequestsOptions`, `PaginateRequestsOptions`, `LogOptions`, `DownloadItemsFormat` — see [options reference](options.md) for the full list |
| `Apify\Client\Http\` | The replaceable transport, its adapters, and compressors. | `HttpClientInterface`, `GuzzleHttpClient`, `Psr18HttpClient`, `HttpCompressorInterface`, `BrotliHttpCompressor`, `GzipHttpCompressor` |
| `Apify\Client\Exception\` | Exceptions thrown by the client. | `ApifyApiException` and its status-specific subclasses (`NotFoundException`, `RateLimitException`, ...), `TransportException` |

Expand Down Expand Up @@ -97,7 +97,7 @@ A few methods return data whose shape is not modelled and is instead returned as
JSON value — typically an associative array, though `getInput()` is typed `mixed` and returns
whatever JSON value was stored (or accept an arbitrary value serialized to JSON):

- Read: `me()->monthlyUsage(...)`, `me()->limits()`, `task($id)->getInput()`,
- Read: `me()->monthlyUsage(...)`, `me()->limits()`, `schedule($id)->getLog()`, `task($id)->getInput()`,
`build($id)->getOpenApiDefinition()`, `dataset($id)->getStatistics()`. Note that
`deleteRequestLock` returns `void` (it releases a lock and has no meaningful body), so it is not in
this list. The request-queue lock/list/unlock/batch-delete operations
Expand Down
2 changes: 1 addition & 1 deletion docs/actors.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ Addressed by ID or `username~name` (a `username/name` is accepted too).
- `update(mixed $newFields): Actor`
- `delete(): void`
- `start(mixed $input = null, ?ActorStartOptions $options = null): ActorRun` — start and return immediately.
- `call(mixed $input = null, ?ActorStartOptions $options = null, ?int $waitSecs = null): ActorRun` — start and wait.
- `call(mixed $input = null, ?ActorStartOptions $options = null, ?int $waitSecs = null, int|float|string|null $timeoutSecs = null): ActorRun` — start and wait. `$timeoutSecs` is the timeout of the start request and of every poll (default `'noTimeout'`).
- `validateInput(mixed $input = null, ?ValidateInputOptions $options = null): bool`

`$input` is a JSON-serializable array (the normal case), or a raw `string` of bytes sent exactly as
Expand Down
2 changes: 1 addition & 1 deletion docs/builds.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ An Actor's builds are available at `$client->actor($id)->builds()`.
- `get(?int $waitForFinishSecs = null): ?Build` — fetch, optionally waiting server-side (max 60s).
- `abort(): Build`
- `delete(): void`
- `waitForFinish(?int $waitSecs = null): Build` — poll until terminal (`null` waits indefinitely).
- `waitForFinish(?int $waitSecs = null, int|float|string|null $timeoutSecs = null): Build` — poll until terminal (`null` waits indefinitely); `$timeoutSecs` is the timeout of each poll request (default `'noTimeout'`).
- `getOpenApiDefinition(): ?array` — the build's generated OpenAPI document. Throws if the build
itself does not exist (a 404 here cannot mean anything else).
- `log(): LogClient` — `get()` throws instead of resolving to `null` on a 404, since there is no ID
Expand Down
4 changes: 2 additions & 2 deletions docs/misc.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ foreach ($client->store()->iterate(new StoreListOptions(search: 'scraper'), 50)
## Users — `$client->me()` / `$client->user($id)`

- `get(): ?User` — for `me()`, private account details are available via `toArray()`.
- `monthlyUsage(?string $date = null): array` — current-account monthly usage (only for `me()`). `$date` is an ISO date in `YYYY-MM-DD` format; the report covers the monthly usage cycle containing that date. Omit it (or pass `null`) to report the current month.
- `limits(): array`, `updateLimits(mixed $newLimits): void` — account limits (only for `me()`).
- `monthlyUsage(?string $date = null): array` — the user's monthly usage. `$date` is an ISO date in `YYYY-MM-DD` format; the report covers the monthly usage cycle containing that date. Omit it (or pass `null`) to report the current month.
- `limits(): array`, `updateLimits(mixed $newLimits): void` — the user's account limits. `monthlyUsage()`, `limits()` and `updateLimits()` work for `me()` and for any user ID the token may access.

```php
$me = $client->me()->get();
Expand Down
12 changes: 11 additions & 1 deletion docs/models.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,17 @@ returns. Each model exposes the commonly-used fields as typed getters; every mod
even fields without a dedicated getter).
- `get(string $key): mixed` — a single raw field by its API name.

Getters return `null` when the field is absent from the API response. The `RequestQueueRequest`
Getters return `null` when the field is absent from the API response.

URL fields the OpenAPI specification declares as `format: uri` — a run's `consoleUrl`, `containerUrl` and
`output` links, the `consoleUrl` and public URLs of storages, a key's `recordPublicUrl`, a task's
`standbyUrl`, a webhook's `requestUrl`, a Store Actor's `url` and `userPictureUrl`, and a user profile's
`pictureUrl` and `websiteUrl` — are returned in the WHATWG URL serialization, as the reference client
does, by the getters, `get()` and `toArray()` alike: an empty path gets a trailing slash
(`https://abc.runs.apify.net` becomes `https://abc.runs.apify.net/`), the scheme and host are lowercased,
a default port is dropped, an internationalized host becomes punycode (needs the optional `intl`
extension) and unsafe characters are percent-encoded. A value that is not an absolute http(s)/ws(s)/ftp
URL is kept as received, so a value compared against a stored URL has to be in that form too. The `RequestQueueRequest`
model is also used as an input object and therefore additionally exposes setters and a constructor
(see [Input models](#input-models)).

Expand Down
16 changes: 16 additions & 0 deletions docs/options.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ Extra filters for `runs()->list()`, combined with a `ListOptions`.
| `status` | `list<string>\|null` | Filter by one or more run statuses (e.g. `SUCCEEDED`, `RUNNING`); sent comma-separated. |
| `startedAfter` | `?string` | Only runs started after this ISO-8601 timestamp. |
| `startedBefore` | `?string` | Only runs started before this ISO-8601 timestamp. |
| `deployment` | `list<string>\|null` | Only Actor Standby runs of these deployments (`SINGLE_TENANT`, `MULTI_TENANT_FREE_USERS`, `MULTI_TENANT_PAYING_USERS`, `MULTI_TENANT_OWNER_PAID`); sent comma-separated. The API requires `status` to hold only non-terminal statuses (`READY`, `RUNNING`, `TIMING-OUT`, `ABORTING`) when it is used. Honoured by `runs()` and `actor($id)->runs()`. |

### `StoreListOptions`
For `store()->list()` / `store()->iterate()`.
Expand All @@ -89,9 +90,24 @@ For `store()->list()` / `store()->iterate()`.
| `includeUnrunnableActors` | `?bool` | Include Actors the current user cannot run. |
| `allowsAgenticUsers` | `?bool` | Filter to Actors that allow agentic users. |
| `responseFormat` | `?string` | The response format (`full`, `agent`). |
| `includeInputSchema` | `?bool` | Include each Actor's compact input schema (only returned when its default build succeeded and defines input properties). |

Supports [`withPagination($offset, $limit)`](#manual-offset-paging-with-withpagination).

### `RunIterateDatasetItemsOptions`
For `run($id)->iterateDatasetItems()`.
| Field | Type | Description |
|---|---|---|
| `offset` | `?int` | Number of dataset rows to skip from the beginning (default 0). |
| `limit` | `?int` | Maximum number of dataset rows to scan (default: no limit). Fewer items are yielded when filters drop some, more when `unwind` splits a row into several. |
| `chunkSize` | `?int` | Maximum number of dataset rows requested per API call (default 1000). |
| `clean` | `?bool` | Yield only non-empty items and skip hidden fields. |
| `fields` / `omit` | `list<string>\|null` | Field names to include / exclude. |
| `skipEmpty` | `?bool` | Skip empty items. |
| `skipHidden` | `?bool` | Skip hidden fields (those starting with `#`). |
| `unwind` | `list<string>\|null` | Field names to unwind: each array value becomes a separate item. |
| `pollIntervalSecs` | `?int` | How long to wait for the run to finish between polls, in seconds (default 5). |

### `LastRunOptions`
For `actor()->lastRun()` / `task()->lastRun()`.
| Field | Type | Description |
Expand Down
Loading
Loading