Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
a809034
refactor!: package split + issue #8 review fixes; sync spec to v2-202…
claude Jul 20, 2026
2a1c031
fix: address loop-1 review — exception hierarchy, internal package, r…
claude Jul 20, 2026
a95c3e8
fix: loop-3 review follow-ups — payload-size chunking, typed RQ retur…
claude Jul 20, 2026
c786760
fix: loop-4 review follow-ups — disclaimer dedup, batchAddRequests ne…
claude Jul 21, 2026
344736b
fix: loop-5 review follow-ups — package-private call/start options, d…
claude Jul 21, 2026
f705ae6
fix: loop-5 review polish — dedupe options-class javadoc, consistent …
claude Jul 21, 2026
35c02cc
fix: loop-5 review round 2 — CI flaky-test fix, P5/P6/P7 correctness+…
claude Jul 21, 2026
d74cd92
fix: loop-6 review follow-ups — transport-failure never-throws, share…
claude Jul 21, 2026
83b41a5
fix: loop-8 correctness fixes — Q1/Q10/Q13
claude Jul 21, 2026
e2beffa
fix: loop-8 DTO parity — Q9/Q11/Q12
claude Jul 21, 2026
67e1a33
fix: loop-8 DRY + magic-number cleanup — Q2/Q3/Q4
claude Jul 21, 2026
643b9b4
fix: loop-8 comments, idiomatic style, CRUD-flow list() coverage, doc…
claude Jul 21, 2026
b423f6b
docs: loop-9 review polish — CHANGELOG process-narrative cleanup, DTO…
claude Jul 22, 2026
27c99bf
fix: nested value DTOs preserve unknown API fields via getExtra()
claude Jul 22, 2026
6399194
docs: fix stale "backend" terminology in docs/README.md
claude Jul 22, 2026
bbab39c
fix: extract error type/data independently of message in buildApiError
claude Jul 22, 2026
e3a0701
fix: gate streamed-log integration assertions on authoritative log pr…
claude Jul 22, 2026
dd5675f
fix: loop-13 review follow-up — call()-default log-streaming tests mu…
claude Jul 22, 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
5 changes: 5 additions & 0 deletions .github/workflows/java-integration-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ concurrency:
group: java-integration-${{ github.ref }}
cancel-in-progress: true

# Least-privilege default: this workflow only checks out code and runs tests, it never writes to
# the repository or opens PRs/issues, so the GITHUB_TOKEN needs no more than read access.
permissions:
contents: read

jobs:
test:
runs-on: ubuntu-latest
Expand Down
247 changes: 229 additions & 18 deletions CHANGELOG.md

Large diffs are not rendered by default.

255 changes: 160 additions & 95 deletions README.md

Large diffs are not rendered by default.

113 changes: 86 additions & 27 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
# Apify Java 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 Java client, organized by resource. Each page
lists the available methods with their parameters and short snippets. The snippets are code
fragments that assume a configured `client` and the imports listed below, not standalone `main`
programs; for complete, runnable programs see [examples.md](examples.md) and
programs; [examples.md](examples.md) has more fragments in the same style, and the complete,
runnable programs live under
[`src/test/java/com/apify/client/examples/`](https://github.com/apify/apify-client-java/tree/master/src/test/java/com/apify/client/examples). For an
overview, configuration, error handling and the full resource table, see the
[top-level README](../README.md).
Expand All @@ -20,7 +17,7 @@ ApifyClient client = ApifyClient.create("my-api-token");

`ApifyClient.create` takes the token as an explicit argument — it does not read `APIFY_TOKEN`
automatically. Use `ApifyClient.builder()` for non-default settings (base URL, retries, timeout,
user-agent suffix, custom HTTP backend).
user-agent suffix, custom HTTP transport).

Get your API token from the
[Apify Console → Settings → API & Integrations](https://console.apify.com/settings/integrations).
Expand All @@ -31,10 +28,39 @@ empty `Optional` rather than an exception. API failures are thrown as `ApifyApiE

## Imports and dependencies

Snippets in these docs assume the client types are imported from `com.apify.client` (e.g.
`import com.apify.client.*;`) plus standard-library types (`java.util.List`, `java.util.ArrayList`,
`java.util.Map`, `java.util.Optional`, `java.util.Iterator`, `java.util.function.Consumer`,
`java.time.Duration`, `java.io.InputStream`).
Client types are **not** all in one package: `import com.apify.client.*;` only resolves
`ApifyClient`, its builder, and the shared kernel types below — a wildcard import does not reach
into sub-packages in Java. Every resource has its own sub-package for its client(s), model(s) and
option types, so import each resource you use from its own package:

| Package | Contains |
|---|---|
| `com.apify.client` (root) | `ApifyClient`, `ApifyClientBuilder`, `Version`, `PaginationList<T>`, `ListOptions`, `StorageListOptions`, `ApifyResource` |
| `com.apify.client.http` | `ApifyClientException`, `ApifyApiException`, `ApifyTransportException`, `HttpTransport`, `DefaultHttpTransport`, `HttpTimeoutException` |
| `com.apify.client.actor` | `Actor`, `ActorClient`, `ActorCollectionClient`, `ActorListOptions`, `ActorStartOptions`, `ActorCallOptions`, `ActorBuildOptions`, `ActorStandby`, `ActorVersion`, `ActorVersionClient`, `ActorVersionCollectionClient`, `ActorEnvVar`, `ActorEnvVarClient`, `ActorEnvVarCollectionClient`, `ValidateInputOptions`, `ActorStats`, `ActorDefaultRunOptions` |
| `com.apify.client.build` | `Build`, `BuildClient`, `BuildCollectionClient`, `BuildMeta`, `BuildStats`, `BuildOptions`, `BuildUsage` |
| `com.apify.client.run` | `ActorRun`, `ActorRunStats`, `ActorRunOptions`, `ActorRunMeta`, `ActorRunUsage`, `RunClient`, `RunCollectionClient`, `RunListOptions`, `LastRunOptions`, `MetamorphOptions`, `RunChargeOptions`, `RunResurrectOptions`, `SetStatusMessageOptions` |
| `com.apify.client.dataset` | `Dataset`, `DatasetClient`, `DatasetCollectionClient`, `DatasetListItemsOptions`, `DatasetDownloadOptions`, `DownloadItemsFormat` |
| `com.apify.client.keyvalue` | `KeyValueStore`, `KeyValueStoreClient`, `KeyValueStoreCollectionClient`, `KeyValueStoreRecord`, `KeyValueStoreKeysPage`, `KeyValueStoreKey`, `GetRecordOptions`, `SetRecordOptions`, `ListKeysOptions` |
| `com.apify.client.requestqueue` | `RequestQueue`, `RequestQueueClient`, `RequestQueueCollectionClient`, `RequestQueueRequest`, `RequestQueueHead`, `LockedRequestQueueHead`, `RequestQueueOperationInfo`, `RequestLockInfo`, `UnlockRequestsResult`, `RequestsList`, `BatchAddResult`, `BatchDeleteResult`, `DeletedRequestInfo`, `ListRequestsOptions`, `BatchAddRequestsOptions` |
| `com.apify.client.task` | `Task`, `TaskStats`, `TaskOptions`, `TaskClient`, `TaskCollectionClient`, `TaskStartOptions`, `TaskCallOptions` |
| `com.apify.client.schedule` | `Schedule`, `ScheduleNotifications`, `ScheduleClient`, `ScheduleCollectionClient` |
| `com.apify.client.webhook` | `Webhook`, `WebhookStats`, `WebhookLastDispatch`, `WebhookClient`, `WebhookCollectionClient`, `WebhookDispatchCollectionClient`, `NestedWebhookCollectionClient`, `WebhookDispatch`, `WebhookDispatchClient`, `WebhookDispatchCall`, `WebhookDispatchWebhookInfo`, `WebhookDispatchEventData` |
| `com.apify.client.user` | `User`, `UserClient`, `UserProfile`, `UserProxy`, `UserPlan`, `ProxyGroup` |
| `com.apify.client.store` | `ActorStoreListItem`, `StoreCollectionClient`, `StoreListOptions`, `PricingInfo` |
| `com.apify.client.log` | `LogClient`, `LogOptions`, `StreamedLog`, `StreamedLogOptions` |

For example, the [top-level README's dataset snippet](../README.md#quick-start) needs
`com.apify.client.ApifyClient`, `com.apify.client.PaginationList`,
`com.apify.client.dataset.DatasetListItemsOptions`, `com.apify.client.run.ActorRun`,
`com.apify.client.actor.ActorStartOptions` and `com.fasterxml.jackson.databind.JsonNode` — five
different packages for one four-line snippet.

Snippets in these docs also assume the standard-library types they use are imported
(`java.util.List`, `java.util.ArrayList`, `java.util.Map`, `java.util.Optional`,
`java.util.Iterator`, `java.util.UUID`, `java.util.function.Consumer`, `java.time.Duration`,
`java.time.Instant`, `java.io.InputStream`). `java.time.Instant` is returned by many model getters
(e.g. `createdAt`, `modifiedAt`, `nextRunAt`, `lockExpiresAt`).

Raw-JSON return values use Jackson's `com.fasterxml.jackson.databind.JsonNode`. Jackson is a
transitive dependency of this client, so it is already on your classpath.
Expand All @@ -44,34 +70,67 @@ transitive dependency of this client, so it is already on your classpath.
A few methods return data whose shape is not modelled by this client and is instead exposed as a
Jackson `JsonNode` (or accept an arbitrary `Object` serialized to JSON):

- Read: `me().monthlyUsage(...)`, `me().limits()`, `task(id).getInput()`,
`build(id).getOpenApiDefinition()`, `dataset(id).getStatistics()` (returned as
`Optional<JsonNode>`), and the raw request-queue operations (`listRequests`, `listAndLockHead`,
`prolongRequestLock`, `unlockRequests`, `batchDeleteRequests`).
- Write: `task(id).updateInput(...)` and `me().updateLimits(...)` accept an arbitrary
JSON-serializable value, as do definition/`update`/`create` arguments generally — a `Map`, a
`JsonNode`, or your own POJO.
- Read, returning a required `JsonNode` (never absent): `me().monthlyUsage(...)`, `me().limits()`.
- Read, returning `Optional<JsonNode>` (empty when the underlying resource has none): `dataset(id).getStatistics()`,
`task(id).getInput()`, `build(id).getOpenApiDefinition()`.
- Write: `task(id).updateInput(...)` (itself returning a required `JsonNode`, the updated input)
and `me().updateLimits(...)` accept an arbitrary JSON-serializable value, as do
definition/`update`/`create` arguments generally — a `Map`, a `JsonNode`, or your own POJO.
- A few typed models still carry one or more raw-JSON fields where the shape is a discriminated
union (or otherwise not worth fully modelling): `RequestQueueRequest.getUserData()`,
`Webhook.getCondition()`, `Schedule.getActions()` (a `List<JsonNode>`), `Task.getInput()` (the
task's stored input, on the `Task` model itself — distinct from the live-fetching
`task(id).getInput()` client call listed above), and `ActorRun`, which carries two:
`getPricingInfo()` and `getStorageIds()`.

Navigate a `JsonNode` with `node.get("field")`, `node.path("a").asText()`, etc.

The request-queue lock/list operations (`listRequests`, `listAndLockHead`, `prolongRequestLock`,
`unlockRequests`, `batchDeleteRequests`) return typed models (`RequestsList`,
`LockedRequestQueueHead`, `RequestLockInfo`, `UnlockRequestsResult`, `BatchDeleteResult`) rather
than raw `JsonNode` — see [Storages](storages.md#request-queues).

## What `ApifyResource` is

`ApifyResource` (root package) is the base class every response model extends — both the top-level
resources (`Actor`, `Dataset`, `Schedule`, `ActorRun`, and so on) and the nested value DTOs embedded
in them (`ActorRunStats`, `ActorRunMeta`, `BuildStats`, `WebhookStats`, `UserProfile`, `PricingInfo`,
and every other nested model in this client). It has no fields of its own beyond the `extra` map
described below; it exists purely so every model shares one place to capture unmodelled API fields,
and so code that only needs the common capability (rare — most call sites use a concrete model type
directly) can accept `ApifyResource` rather than a specific model.

## Model fields and unmodeled data (`getExtra`)

Response models expose the commonly-used fields as typed getters. The API returns more fields than
are modelled; every model also carries a `getExtra()` map (`Map<String, Object>`) holding any field
not mapped to a typed getter, so nothing the API returns is lost. For example a `Schedule`'s
`actions`/`isExclusive`, or a `me()` `User`'s private account details (email, plan, proxy settings,
…), are available via `getExtra()`.
are modelled; every model — including nested value DTOs, not only the top-level resources — also
carries a `getExtra()` map (`Map<String, Object>`, inherited from `ApifyResource` above) holding any
field not mapped to a typed getter, so nothing the API returns is lost, now or as the API grows new
fields later. For example a `me()` `User`'s private account details (email, plan, proxy settings,
…) are available via `getExtra()`, since `User` models only its most commonly used fields.

```java
Schedule schedule = client.schedule("SCHEDULE_ID").get().orElseThrow();
Object actions = schedule.getExtra().get("actions");
User me = client.me().get().orElseThrow();
Object plan = me.getExtra().get("plan");
```

## Setting single-resource status
## Setting the current run's status message

`client.setStatusMessage(String message, boolean isTerminal)` updates the status message of the
current Actor run (identified by the `ACTOR_RUN_ID` environment variable); it only works from inside
a run and throws `IllegalStateException` otherwise. Returns the updated `ActorRun`.
`client.setStatusMessage(String message, SetStatusMessageOptions)` (import from
`com.apify.client.run`) updates the status message of the current Actor run (identified by the
`ACTOR_RUN_ID` environment variable); it only works from inside a run and throws
`IllegalStateException` otherwise. `SetStatusMessageOptions.isStatusMessageTerminal(boolean)` marks
the message as final so it won't be overwritten. Returns the updated `ActorRun`.

Note: `isStatusMessageTerminal(boolean)` takes a primitive `boolean`, not a boxed `Boolean` — an
intentional exception to the "optional fields are boxed so they can stay unset" convention used
elsewhere in this client's options classes, since this field always has a concrete on/off meaning
with no useful "unset" state (the field is simply omitted from the request body when the setter is
never called).

```java
client.setStatusMessage("half way there", new SetStatusMessageOptions().isStatusMessageTerminal(false));
```

## Optional option fields

Expand Down
48 changes: 32 additions & 16 deletions docs/actors.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
# Actors, versions & environment variables

> **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.

Access the Actor collection with `client.actors()` and a single Actor with `client.actor(id)`,
where `id` is an Actor ID or `username~name` (a `/` in the id is accepted and normalized).

Expand Down Expand Up @@ -51,26 +47,35 @@ Actor created = client.actors().create(Map.of(
| `update(Object)` | Update the Actor with the given fields. Returns `Actor`. |
| `delete()` | Delete the Actor. |
| `start(Object input, ActorStartOptions)` | Start a run, returning immediately. Returns `ActorRun`. |
| `call(Object input, ActorStartOptions, Long waitSecs)` | Start a run and poll until it finishes (`null` waits indefinitely). Returns `ActorRun`. |
| `validateInput(Object input)` / `validateInput(Object input, ValidateInputOptions)` | Validate an input against the Actor's input schema. Returns `boolean`. `ValidateInputOptions` fields (optional): `build`, `contentType`. |
| `call(Object input, ActorStartOptions, Long waitSecs)` | Start a run and poll until it finishes (`null` waits indefinitely); does **not** stream the run's log. Returns `ActorRun`. |
| `call(Object input, ActorCallOptions, Long waitSecs)` | As above, additionally streaming the run's log for the duration of the wait by default (matching the reference client's `call` defaulting `options.log` to `'default'`). Use `ActorCallOptions.disableLogStreaming()` to opt out, or `logOptions(StreamedLogOptions)` for a custom destination. |
| `validateInput(Object input)` / `validateInput(Object input, ValidateInputOptions)` | Validate an input against the Actor's input schema. Returns `boolean`. `ValidateInputOptions` fields (optional): `build` (`String`), `contentType` (`String`). |
| `build(String versionNumber, ActorBuildOptions)` | Build a version. Returns `Build`. |
| `defaultBuild(Long waitForFinish)` | Resolve the default build. Returns `BuildClient`. |
| `lastRun(String status)` / `lastRun(LastRunOptions)` | A `RunClient` for the last run. |
| `builds()` / `runs()` / `versions()` | Nested collection clients. |
| `webhooks()` | Read-only nested webhook collection (`NestedWebhookCollectionClient`, `list` + `iterate`, no `create`). |
| `version(String)` | An `ActorVersionClient`. |

`ActorStartOptions` fields (all optional): `build` (the tag or number of the build to run, e.g.
`latest`, `0.1.2`), `memoryMbytes` (memory in megabytes allocated for the run), `timeoutSecs` (run
timeout in seconds; `0` means no timeout), `waitForFinish` (maximum seconds to wait server-side for
the run to finish, max 60), `maxItems` (maximum dataset items to charge, pay-per-result Actors),
`maxTotalChargeUsd` (maximum total charge in USD, pay-per-event Actors), `contentType` (content type
of the input body, defaults to `application/json`), `restartOnError` (restart the run if it fails),
`forcePermissionLevel` (override the Actor's permission level for this run:
`ActorStartOptions` fields (all optional): `build(String)` (the tag or number of the build to run,
e.g. `latest`, `0.1.2`), `memoryMbytes(Long)` (memory in megabytes allocated for the run),
`timeoutSecs(Long)` (run timeout in seconds; `0` means no timeout), `waitForFinish(Long)` (maximum
seconds to wait server-side for the run to finish, max 60), `maxItems(Long)` (maximum dataset items
to charge, pay-per-result Actors), `maxTotalChargeUsd(Double)` (maximum total charge in USD,
pay-per-event Actors), `contentType(String)` (content type of the input body, defaults to
`application/json`), `restartOnError(Boolean)` (restart the run if it fails),
`forcePermissionLevel(String)` (override the Actor's permission level for this run:
`LIMITED_PERMISSIONS`/`FULL_PERMISSIONS`), and `webhooks(List<Object>)` — ad-hoc webhook definitions
(each a JSON-serializable `Map`, as in [Webhooks](webhooks.md)) that the client base64-encodes on the
wire.

`ActorCallOptions` (for the log-streaming `call` overload) mirrors `ActorStartOptions` field for
field, but omits `waitForFinish`: that field asks the API to hold the HTTP response open
server-side while the run finishes, which is redundant with (and wastes a request slot next to)
`call`'s own client-side `waitSecs` polling. It adds `disableLogStreaming()` (matching the
reference client's `log: null`) and `logOptions(StreamedLogOptions)` (a custom destination/prefix,
matching a custom `Log` instance) — see [Streamed log redirection](runs.md#streamed-log-redirection).

`lastRun(String status)` filters only by status; `lastRun(LastRunOptions)` also accepts an origin
filter. `LastRunOptions` has fluent setters `status(String)` (e.g. `SUCCEEDED`, `RUNNING`) and
`origin(String)` (e.g. `API`, `WEB`, `SCHEDULER`); leave a setter uncalled to omit that filter.
Expand All @@ -87,7 +92,18 @@ System.out.println(run.getStatus());
```

`Actor` fields: `getId()`, `getUserId()`, `getName()`, `getUsername()`, `getTitle()`,
`getDescription()`, `isPublic()`, `getCreatedAt()`, `getModifiedAt()`, plus `getExtra()` for any
`getDescription()`, `isPublic()`, `getCreatedAt()`, `getModifiedAt()`, `getStats()` (`ActorStats` —
`getTotalBuilds()`/`getTotalRuns()`/`getTotalUsers()`/`getTotalUsers7Days()`/
`getTotalUsers30Days()`/`getTotalUsers90Days()`/`getTotalMetamorphs()` as `Long`,
`getLastRunStartedAt()` as `Instant`), `getVersions()` (`List<ActorVersion>`),
`getPricingInfos()` (`List<JsonNode>`; shape depends on pricing model), `getDefaultRunOptions()`
(`ActorDefaultRunOptions` — `getBuild()`, `getTimeoutSecs()`/`getMemoryMbytes()` as `Long`,
`getRestartOnError()` as `Boolean`), `getTaggedBuilds()` (`JsonNode`; dynamic tag-name keys),
`getIsDeprecated()` (`Boolean`), `getDeploymentKey()`, `getSeoTitle()`, `getSeoDescription()`,
`getCategories()` (`List<String>`), `getActorStandby()` (`ActorStandby`, from
`com.apify.client.actor`, nullable — see [Tasks](tasks.md) for its field list; only on `Actor` does
it additionally expose
`getIsEnabled()`), `getActorPermissionLevel()` (e.g. `"OWNER"`), plus `getExtra()` for any
unmodelled fields.

## `ActorVersionClient` and `ActorVersionCollectionClient`
Expand All @@ -99,8 +115,8 @@ and deletes a single version and exposes its environment variables.

| Method | Description |
|---|---|
| `list(ListOptions)` | List the Actor's versions. Returns `PaginationList<ActorVersion>`. |
| `iterate(ListOptions)` | Lazy `Iterator<ActorVersion>` over all versions; `limit` caps the total (`null`/unset or non-positive = all). The versions endpoint is not paginated (one fetch returns every version), so `offset` has no effect and there is no page size to tune. |
| `list(ListOptions)` | List the Actor's versions. Returns `PaginationList<ActorVersion>`. The endpoint takes no query parameters; `options` (`offset`/`limit`/`desc`) is accepted only for signature consistency with every other `list(ListOptions)` and has no effect — pass `null`. |
| `iterate(ListOptions)` | Lazy `Iterator<ActorVersion>` over all versions; `limit` caps the total (`null`/unset or non-positive = all), applied client-side after the single fetch. `offset` has no effect and there is no page size to tune. |
| `create(Object version)` | Create a version. Returns `ActorVersion`. |

### `ActorVersionClient` — `client.actor(id).version(v)`
Expand Down
Loading
Loading