diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7189d4d..68ba21f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,15 @@
# Changelog
+## 0.4.0
+
+- Synced to Apify OpenAPI spec `v2-2026-08-05T133145Z` (additive nullability/response/description
+ changes only; no client code changes required beyond the version constant).
+- Added `TaskClient::publish()` and `TaskClient::unpublish()` convenience methods (mirroring the
+ reference JS client), plus `Task::isPublic()` and `Task::getPublicConfig()` getters.
+- Removed the duplicated "official, but experimental, AI-generated" disclaimer from
+ `docs/README.md` and the `ApifyClient` class docblock; it is now stated only once, in the
+ top-level `README.md`.
+
## 0.3.4
- Synced to Apify OpenAPI spec `v2-2026-07-13T092445Z` (adds `402`/`408` error responses to the
diff --git a/docs/README.md b/docs/README.md
index f63782c..65ed59c 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1,9 +1,5 @@
# Apify PHP client documentation
-> **Official, but experimental — AI-generated and AI-maintained.** This is an official Apify client,
-> but it is experimental: it is generated and maintained by AI. Review the code before relying on it
-> in production and report issues on the repository.
-
This directory documents the public API of the Apify PHP client, organized by resource. Each page
lists the available methods with their parameters and short, runnable snippets. For an overview,
configuration and error handling, see the [top-level README](../README.md).
diff --git a/docs/models.md b/docs/models.md
index a8317e9..9c8f24b 100644
--- a/docs/models.md
+++ b/docs/models.md
@@ -72,6 +72,8 @@ model is also used as an input object and therefore additionally exposes setters
| `getTitle(): ?string` | Human-readable title. |
| `getCreatedAt(): ?string` | ISO-8601 creation timestamp. |
| `getModifiedAt(): ?string` | ISO-8601 last-modification timestamp. |
+| `isPublic(): ?bool` | Whether the task is published on its public landing page. |
+| `getPublicConfig(): ?array` | Public landing page display configuration, or `null` if not published. |
### `Schedule`
| Getter | Description |
diff --git a/docs/tasks.md b/docs/tasks.md
index bd186d6..cf7f54a 100644
--- a/docs/tasks.md
+++ b/docs/tasks.md
@@ -24,6 +24,9 @@ foreach ($client->tasks()->iterate(new ListOptions(), 50) as $t) {
## A single task — `$client->task($id)`
- `get(): ?Task`, `update(mixed $newFields): Task`, `delete(): void`
+- `publish(): Task`, `unpublish(): Task` — publish/unpublish the task's public landing page, by
+ setting `isPublic` through `update()`. Publishing requires the task's Actor to be public and the
+ task to already have its `publicConfig` set up.
- `start(mixed $input = null, ?TaskStartOptions $options = null): ActorRun`
- `call(mixed $input = null, ?TaskStartOptions $options = null, ?int $waitSecs = null): ActorRun`
- `getInput(): mixed`, `updateInput(mixed $input): mixed`
@@ -34,4 +37,5 @@ foreach ($client->tasks()->iterate(new ListOptions(), 50) as $t) {
```php
$run = $client->task('me~my-task')->call(['message' => 'override'], null, 120);
$input = $client->task('me~my-task')->getInput();
+$client->task('me~my-task')->publish();
```
diff --git a/src/ApifyClient.php b/src/ApifyClient.php
index ecdead7..d380cb0 100644
--- a/src/ApifyClient.php
+++ b/src/ApifyClient.php
@@ -42,10 +42,6 @@
/**
* The entry point for interacting with the Apify API.
*
- * Official, but experimental — AI-generated and AI-maintained. This is an official Apify
- * client, but it is experimental: it is generated and maintained by AI. Review the code before
- * relying on it in production and report issues on the repository.
- *
* Construct it with an API token (and optional settings via named arguments), then obtain resource
* clients via the accessor methods, e.g. {@see actor()}, {@see dataset()}, {@see run()}.
*
diff --git a/src/Model/Task.php b/src/Model/Task.php
index 9cb1dd0..693af48 100644
--- a/src/Model/Task.php
+++ b/src/Model/Task.php
@@ -48,4 +48,27 @@ public function getModifiedAt(): ?string
{
return $this->getString('modifiedAt');
}
+
+ /**
+ * Whether the task is published on its public landing page. Derived from
+ * {@code publicConfig.publishedAt} — use {@see TaskClient::publish()} and
+ * {@see TaskClient::unpublish()} to change it.
+ */
+ public function isPublic(): ?bool
+ {
+ return $this->getBool('isPublic');
+ }
+
+ /**
+ * The public-facing display configuration of the task's public landing page, or {@code null}
+ * if the task is not published. Contains fields such as {@code publishedAt}, {@code seoTitle}
+ * and {@code datasetView}.
+ *
+ * @return array|null
+ */
+ public function getPublicConfig(): ?array
+ {
+ $value = $this->get('publicConfig');
+ return is_array($value) ? $value : null;
+ }
}
diff --git a/src/Resource/TaskClient.php b/src/Resource/TaskClient.php
index 437c04c..e1f821a 100644
--- a/src/Resource/TaskClient.php
+++ b/src/Resource/TaskClient.php
@@ -57,6 +57,32 @@ public function delete(): void
$this->ctx->deleteResource('');
}
+ /**
+ * Publishes the task on its public landing page, by setting {@code isPublic} through
+ * {@see update()}.
+ *
+ * The task's Actor must be public and the task must have its public display configuration
+ * ({@code publicConfig}) set up first. Requires write permission to both the task and its
+ * Actor. Publishing an already published task does nothing.
+ */
+ public function publish(): Task
+ {
+ return $this->update(['isPublic' => true]);
+ }
+
+ /**
+ * Unpublishes the task from its public landing page, by setting {@code isPublic} through
+ * {@see update()}.
+ *
+ * The public display configuration ({@code publicConfig}) is preserved, so the task can be
+ * published again without re-entering it. Requires write permission to both the task and its
+ * Actor. Unpublishing a task that is not published does nothing.
+ */
+ public function unpublish(): Task
+ {
+ return $this->update(['isPublic' => false]);
+ }
+
/**
* Starts the task and returns immediately with the created run.
*
diff --git a/src/Version.php b/src/Version.php
index 023c199..315d313 100644
--- a/src/Version.php
+++ b/src/Version.php
@@ -17,13 +17,13 @@ final class Version
* The semantic version of this client library (see https://semver.org/).
* Changes to the public interface other than additive ones are considered breaking changes.
*/
- public const CLIENT_VERSION = '0.3.4';
+ public const CLIENT_VERSION = '0.4.0';
/**
* The version of the Apify OpenAPI specification this client was generated and verified
* against. Corresponds to the {@code info.version} field of the Apify OpenAPI document.
*/
- public const API_SPEC_VERSION = 'v2-2026-07-13T092445Z';
+ public const API_SPEC_VERSION = 'v2-2026-08-05T133145Z';
private function __construct()
{
diff --git a/tests/Integration/TaskIntegrationTest.php b/tests/Integration/TaskIntegrationTest.php
index 58601de..14f33be 100644
--- a/tests/Integration/TaskIntegrationTest.php
+++ b/tests/Integration/TaskIntegrationTest.php
@@ -4,6 +4,7 @@
namespace Apify\Client\Tests\Integration;
+use Apify\Client\Exception\ApifyApiException;
use Apify\Client\Options\ListOptions;
use Apify\Client\Options\RunListOptions;
@@ -66,6 +67,32 @@ public function testIterateTasks(): void
}
}
+ public function testPublishUnpublish(): void
+ {
+ $client = $this->requireClient();
+ $task = $client->tasks()->create(self::taskDef(self::uniqueName('task-publish')));
+ try {
+ $tc = $client->task((string) $task->getId());
+
+ // unpublish() is safe to call even on an unpublished task (no publicConfig required)
+ // and always echoes the isPublic value back, unlike publish() below.
+ $unpublished = $tc->unpublish();
+ self::assertFalse($unpublished->isPublic());
+
+ // publish() requires write permission to the task's Actor. The task's Actor
+ // (apify/hello-world) is not owned by the test account, so this is expected to fail
+ // with a 403 rather than silently succeed.
+ try {
+ $tc->publish();
+ self::fail('expected publish() to fail: the task Actor is not owned by the test account');
+ } catch (ApifyApiException $e) {
+ self::assertSame(403, $e->getStatusCode());
+ }
+ } finally {
+ $client->task((string) $task->getId())->delete();
+ }
+ }
+
public function testTaskCrudFlow(): void
{
$client = $this->requireClient();