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
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,21 @@ All notable changes to the Apify Java client are documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.10.0] - 2026-10-21

### Added

- `RunChargeOptions.requestId(String)`, sent as `requestId` in the charge request body: the ID of the
Server Actor request being charged, required for multi-tenant Server Actor runs.
- `Build.getActVersion()` and the `BuildActVersion` model (with the `SourceCodeFile` model for its
inline `sourceFiles`); `buildTag` is `null` for a version without a build tag.

### Changed

- Updated to Apify OpenAPI specification `v2-2026-10-20T121847Z`.
- Documented that the `FLAT_PRICE_PER_MONTH` (rental) pricing model is deprecated and no longer
listed as a Store `pricingModel` filter value.

## [0.9.0] - 2026-10-19

### Added
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Maven (Maven Central is a default repository, so no extra configuration is neede
<dependency>
<groupId>com.apify</groupId>
<artifactId>apify-client</artifactId>
<version>0.9.0</version>
<version>0.10.0</version>
</dependency>
```

Expand All @@ -35,7 +35,7 @@ repositories {
}

dependencies {
implementation 'com.apify:apify-client:0.9.0'
implementation 'com.apify:apify-client:0.10.0'
}
```

Expand Down Expand Up @@ -317,10 +317,10 @@ try {
The public `com.apify.client.Version` class (`import com.apify.client.Version;`) exposes two
constants:

- `Version.CLIENT_VERSION` — the semantic version of this client (`0.9.0`).
- `Version.CLIENT_VERSION` — the semantic version of this client (`0.10.0`).
- `Version.API_SPEC_VERSION` — the version of the [Apify OpenAPI specification](https://docs.apify.com/api/openapi.json)
(its `info.version` field) that this client's endpoints, parameters and models were last generated
and checked against (`v2-2026-10-08T073338Z`). It is a snapshot, not a live compatibility
and checked against (`v2-2026-10-20T121847Z`). It is a snapshot, not a live compatibility
guarantee: the client keeps working against newer, backward-compatible spec revisions, but a
feature added to the API after this snapshot has no corresponding method here yet.

Expand Down
4 changes: 2 additions & 2 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ option types, so import each resource you use from its own package:
|---|---|
| `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.actor` | `Actor`, `ActorClient`, `ActorCollectionClient`, `ActorListOptions`, `ActorStartOptions`, `ActorCallOptions`, `ActorBuildOptions`, `ActorStandby`, `ActorVersion`, `SourceCodeFile`, `ActorVersionClient`, `ActorVersionCollectionClient`, `ActorEnvVar`, `ActorEnvVarClient`, `ActorEnvVarCollectionClient`, `ValidateInputOptions`, `ActorStats`, `ActorDefaultRunOptions` |
| `com.apify.client.build` | `Build`, `BuildClient`, `BuildCollectionClient`, `BuildActVersion`, `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` |
Expand Down
6 changes: 4 additions & 2 deletions docs/builds.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ builds) and a single build with `client.build(id)`.
| `log()` | A `LogClient` for the build's log. |

`Build` fields: `getId()`, `getActId()`, `getUserId()`, `getStatus()`, `getStartedAt()`,
`getFinishedAt()`, `getBuildNumber()`, `getImageDigest()` (`String`, nullable — the built Docker
`getFinishedAt()`, `getBuildNumber()`, `getActVersion()` (`BuildActVersion` — `getSourceType()`, `getBuildTag()`
(nullable), `getVersionNumber()`, `getGitRepoUrl()`, `getSourceFiles()` (`List<SourceCodeFile>` — `getName()`,
`getFormat()`, `getContent()`)), `getImageDigest()` (`String`, nullable — the built Docker
image manifest's digest, without the `sha256:` prefix; compare two builds' digests to tell whether
their image contents differ), `getMeta()` (`BuildMeta` — `getOrigin()`, `getClientIp()`,
`getUserAgent()`), `getStats()` (`BuildStats` — `getDurationMillis()`/`getRunTimeSecs()` as `Long`,
Expand All @@ -32,7 +34,7 @@ their image contents differ), `getMeta()` (`BuildMeta` — `getOrigin()`, `getCl
`getUsage()` / `getUsageUsd()` (`BuildUsage` — `getActorComputeUnits()` as `Double`, the latter as
its USD cost), `getUsageTotalUsd()` (`Double`), plus `isTerminal()` and `getExtra()`. The status is
one of `READY`, `RUNNING`, `SUCCEEDED`, `FAILED`, `TIMING-OUT`, `TIMED-OUT`, `ABORTING`, `ABORTED`.
Every nested model above (`BuildMeta`, `BuildStats`, `BuildOptions`, `BuildUsage`) also has its own
Every nested model above (`BuildActVersion`, `BuildMeta`, `BuildStats`, `BuildOptions`, `BuildUsage`) also has its own
`getExtra()`, so a field not yet listed here is still reachable rather than dropped.

```java
Expand Down
3 changes: 2 additions & 1 deletion docs/misc.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ Browse public Actors in the Apify Store. `client.store()` returns a `StoreCollec
`StoreListOptions` fields: `offset` (number of Actors to skip), `limit` (maximum number of Actors to
return), `search` (full-text search query), `sortBy` (the sort field, e.g. `popularity`, `newest`),
`category` (filter Actors by category), `username` (filter Actors by owner username), `pricingModel`
(filter by pricing model: `FREE`, `FLAT_PRICE_PER_MONTH`, `PRICE_PER_DATASET_ITEM`, `PAY_PER_EVENT`),
(filter by pricing model: `FREE`, `PRICE_PER_DATASET_ITEM`, `PAY_PER_EVENT`; the deprecated rental model
`FLAT_PRICE_PER_MONTH` is no longer listed),
`includeUnrunnableActors` (include Actors the current user cannot run), `allowsAgenticUsers` (only
Actors that allow agentic users), `responseFormat` (the response shape: `full` or `agent`).

Expand Down
7 changes: 4 additions & 3 deletions docs/runs.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,9 +151,10 @@ nullable), `getMemoryMbytes()` (`Long`, nullable), `getDiskMbytes()` (`Long`, nu
`getMaxItems()` (`Long`, nullable), `getMaxTotalChargeUsd()` (`Double`, nullable),
`getRestartOnError()` (`Boolean`, nullable).

`RunChargeOptions` (constructed with the required event name) uses plain values: `count(Long)` and
`idempotencyKey(String)` — the latter is auto-generated when unset so a transport-retried charge is
applied at most once.
`RunChargeOptions` (constructed with the required event name) uses plain values: `count(Long)`,
`idempotencyKey(String)` — auto-generated when unset so a transport-retried charge is applied at
most once — and `requestId(String)`, the `X-Actor-Request-ID` of the Server Actor request being
charged (required for multi-tenant Server Actor runs, omitted otherwise).

```java
client.run("RUN_ID").charge(new RunChargeOptions("my-event").count(3L)).join();
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>com.apify</groupId>
<artifactId>apify-client</artifactId>
<version>0.9.0</version>
<version>0.10.0</version>
<packaging>jar</packaging>

<name>Apify Java Client</name>
Expand Down
7 changes: 5 additions & 2 deletions spotbugs-exclude.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@

2. UWF_UNWRITTEN_FIELD / UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD on Jackson-populated DTOs/response
shapes ({@code KeyValueStoreKey}, {@code DeletedRequestInfo}, {@code ActorRunMeta}, {@code
TaskOptions}, {@code WebhookLastDispatch}, {@code ActorDefaultRunOptions}, {@code BuildMeta},
{@code BuildOptions}, {@code BuildStats}, {@code PricingInfo}, {@code ProxyGroup}, {@code
TaskOptions}, {@code WebhookLastDispatch}, {@code ActorDefaultRunOptions}, {@code BuildActVersion},
{@code BuildMeta}, {@code BuildOptions}, {@code BuildStats}, {@code PricingInfo}, {@code ProxyGroup}, {@code
UserProxy}, {@code WebhookDispatchEventData}, {@code WebhookDispatchWebhookInfo}, the internal
error-response mapping in {@code HttpClientCore}): their fields are written by the
deserializer via reflection, which SpotBugs cannot see - a known false positive for
Expand Down Expand Up @@ -74,6 +74,8 @@
<Class name="com.apify.client.task.TaskOptions"/>
<Class name="com.apify.client.webhook.WebhookLastDispatch"/>
<Class name="com.apify.client.actor.ActorDefaultRunOptions"/>
<Class name="com.apify.client.build.BuildActVersion"/>
<Class name="com.apify.client.actor.SourceCodeFile"/>
<Class name="com.apify.client.build.BuildMeta"/>
<Class name="com.apify.client.build.BuildOptions"/>
<Class name="com.apify.client.build.BuildStats"/>
Expand Down Expand Up @@ -111,6 +113,7 @@
<Match>
<Class name="com.apify.client.build.Build"/>
<Or>
<Method name="getActVersion"/>
<Method name="getMeta"/>
<Method name="getOptions"/>
<Method name="getStats"/>
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/com/apify/client/Version.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ public final class Version {
* The semantic version of this client library (see <a href="https://semver.org/">SemVer</a>).
* Changes to the public interface other than additive ones are considered breaking changes.
*/
public static final String CLIENT_VERSION = "0.9.0";
public static final String CLIENT_VERSION = "0.10.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 static final String API_SPEC_VERSION = "v2-2026-10-08T073338Z";
public static final String API_SPEC_VERSION = "v2-2026-10-20T121847Z";

private Version() {}
}
28 changes: 28 additions & 0 deletions src/main/java/com/apify/client/actor/SourceCodeFile.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.apify.client.actor;

import com.apify.client.ApifyResource;

/** A single file in an Actor's source code. */
public final class SourceCodeFile extends ApifyResource {
private String name;
private String format;
private String content;

/** The path of the file relative to the Actor's root directory. */
public String getName() {
return name;
}

/**
* The format of {@link #getContent()}: {@code "TEXT"} for plain text, {@code "BASE64"} for
* encoded content.
*/
public String getFormat() {
return format;
}

/** The contents of the file, interpreted according to {@link #getFormat()}. */
public String getContent() {
return content;
}
}
9 changes: 9 additions & 0 deletions src/main/java/com/apify/client/build/Build.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public final class Build extends ApifyResource {
private Instant finishedAt;
private String buildNumber;
private String imageDigest;
private BuildActVersion actVersion;
private BuildMeta meta;
private BuildStats stats;
private BuildOptions options;
Expand Down Expand Up @@ -69,6 +70,14 @@ public String getImageDigest() {
return imageDigest;
}

/**
* A snapshot of the Actor version this build was created from. Its {@link
* BuildActVersion#getBuildTag()} is {@code null} for a version without a build tag.
*/
public BuildActVersion getActVersion() {
return actVersion;
}

/** Metadata about how the build was initiated. */
public BuildMeta getMeta() {
return meta;
Expand Down
50 changes: 50 additions & 0 deletions src/main/java/com/apify/client/build/BuildActVersion.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.apify.client.build;

import com.apify.client.ApifyResource;
import com.apify.client.actor.SourceCodeFile;
import java.util.Collections;
import java.util.List;

/** A snapshot of the Actor version a {@link Build} was created from. */
public final class BuildActVersion extends ApifyResource {
private String sourceType;
private String buildTag;
private String versionNumber;
private String gitRepoUrl;
private List<SourceCodeFile> sourceFiles;

/**
* How the version's source was provided (e.g. {@code "SOURCE_FILES"}, {@code "GIT_REPO"}, {@code
* "TARBALL"}, {@code "GITHUB_GIST"}, {@code "SOURCE_CODE"}).
*/
public String getSourceType() {
return sourceType;
}

/**
* The build tag of the Actor version the build was created from. {@code null} when the version
* has no build tag, in which case the build is tagged on success only if a tag was passed when
* starting it.
*/
public String getBuildTag() {
return buildTag;
}

/** The version number (e.g. {@code "0.1"}). */
public String getVersionNumber() {
return versionNumber;
}

/** URL of the Git repository; present when the source type is {@code GIT_REPO}. */
public String getGitRepoUrl() {
return gitRepoUrl;
}

/**
* The inline source files; non-empty only when the source type is {@code SOURCE_FILES}, an empty
* list otherwise.
*/
public List<SourceCodeFile> getSourceFiles() {
return sourceFiles == null ? List.of() : Collections.unmodifiableList(sourceFiles);
}
}
15 changes: 15 additions & 0 deletions src/main/java/com/apify/client/run/RunChargeOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ public final class RunChargeOptions {
private String eventName;
private Long count;
private String idempotencyKey;
private String requestId;

/** Creates options for the given (required) event name. */
public RunChargeOptions(String eventName) {
Expand Down Expand Up @@ -32,6 +33,16 @@ public RunChargeOptions idempotencyKey(String idempotencyKey) {
return this;
}

/**
* The ID of the Server Actor request being charged, taken from the {@code X-Actor-Request-ID}
* header of the incoming request. Required for multi-tenant Server Actor runs, where it
* identifies the request the charge belongs to; omit it otherwise.
*/
public RunChargeOptions requestId(String requestId) {
this.requestId = requestId;
return this;
}

String eventNameValue() {
return eventName;
}
Expand All @@ -43,4 +54,8 @@ long countValue() {
String idempotencyKeyValue() {
return idempotencyKey;
}

String requestIdValue() {
return requestId;
}
}
4 changes: 4 additions & 0 deletions src/main/java/com/apify/client/run/RunClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,10 @@ public CompletableFuture<Void> charge(RunChargeOptions options) {
Map<String, Object> body = new LinkedHashMap<>();
body.put("eventName", eventName);
body.put("count", options.countValue());
String requestId = options.requestIdValue();
if (requestId != null && !requestId.isEmpty()) {
body.put("requestId", requestId);
}
Map<String, String> headers = Map.of(CHARGE_IDEMPOTENCY_HEADER, idempotencyKey);
// Route through mergedParams like the run's other actions, so a last-run-seeded context charges
// the same run its read methods resolve to (its pinned status/origin filters are preserved).
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/apify/client/store/PricingInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ public final class PricingInfo extends ApifyResource {
private String pricingModel;

/**
* The pricing model, e.g. {@code "FREE"}, {@code "FLAT_PRICE_PER_MONTH"}, {@code
* The pricing model, e.g. {@code "FREE"}, {@code "PRICE_PER_DATASET_ITEM"}, {@code
* "PAY_PER_EVENT"}.
*/
public String getPricingModel() {
Expand Down
5 changes: 3 additions & 2 deletions src/main/java/com/apify/client/store/StoreListOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,9 @@ public StoreListOptions username(String username) {
}

/**
* Filter Actors by pricing model ({@code FREE}, {@code FLAT_PRICE_PER_MONTH}, {@code
* PRICE_PER_DATASET_ITEM}, {@code PAY_PER_EVENT}).
* Filter Actors by pricing model ({@code FREE}, {@code PRICE_PER_DATASET_ITEM}, {@code
* PAY_PER_EVENT}). The rental model ({@code FLAT_PRICE_PER_MONTH}) is deprecated and no longer
* supported, so it is no longer documented as a filter value and matches no Actors.
*/
public StoreListOptions pricingModel(String pricingModel) {
this.pricingModel = pricingModel;
Expand Down
14 changes: 14 additions & 0 deletions src/test/java/com/apify/client/ClientBehaviourRegressionTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,20 @@ void chargeHonorsExplicitIdempotencyKey() {
assertEquals("fixed-key", backend.lastHeaders.firstValue("idempotency-key").orElse(""));
}

@Test
void chargeSendsRequestIdOnlyWhenSet() {
MockTransport withId = MockTransport.ofConstant(200, "{\"data\":{}}");
client(withId)
.run("run123")
.charge(new RunChargeOptions("e").requestId("7Hs2kQw9LxP4mN1vB"))
.join();
assertTrue(withId.lastBody.contains("\"requestId\":\"7Hs2kQw9LxP4mN1vB\""), withId.lastBody);

MockTransport without = MockTransport.ofConstant(200, "{\"data\":{}}");
client(without).run("run123").charge(new RunChargeOptions("e")).join();
assertFalse(without.lastBody.contains("requestId"), without.lastBody);
}

@Test
void chargeRejectsMissingEventName() {
MockTransport backend = MockTransport.ofConstant(200, "{\"data\":{}}");
Expand Down
Loading
Loading