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
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# Changelog

## 0.1.2

- Bumped `ApifyClientVersion.ApiSpecVersion` to the Apify OpenAPI spec `v2-2026-07-08T143931Z` and the
project version to `0.1.2`.
- Aligned the `User-Agent` OS token with the reference client's Node `os.platform()` values (`win32`,
`darwin`, `linux`, `android`, `freebsd`, and — for platforms without a dedicated .NET helper —
`openbsd`, `netbsd`, `sunos`, `aix`) instead of `windows`.
- Request bodies of at least 1024 bytes are compressed before sending. The compression algorithm is
selectable via the new `ApifyClientOptions.RequestCompression` option (`RequestCompression` enum):
brotli (`Content-Encoding: br`) by default, or gzip (`Content-Encoding: gzip`).

## 0.1.1

- Bumped `ApifyClientVersion.ApiSpecVersion` to the Apify OpenAPI spec `v2-2026-07-07T132551Z` and the
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ var run = await client.Actor("apify/hello-world").CallAsync(null, null, null);

// Read items from the run's default dataset.
var items = await client.Dataset(run.DefaultDatasetId!).ListItemsAsync();
Console.WriteLine("Item count: " + items.Count);
// Count is the number of items in THIS page; Total is the dataset's full count across all pages.
Console.WriteLine($"Items on this page: {items.Count} (of {items.Total} total)");
```

`new ApifyClient("my-api-token")` takes the token as an explicit argument — it does **not** read
Expand Down
13 changes: 10 additions & 3 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,14 @@ var run = await client.Actor("apify/hello-world").CallAsync(null, null, null);

// Read items from the run's default dataset.
var items = await client.Dataset(run.DefaultDatasetId!).ListItemsAsync();
Console.WriteLine("Item count: " + items.Count);
// Count is the number of items in THIS page; Total is the dataset's full count across all pages.
Console.WriteLine($"Items on this page: {items.Count} (of {items.Total} total)");
```

`new ApifyClient("my-api-token")` takes the token as an explicit argument — it does **not** read
`APIFY_TOKEN` (or any other environment variable) automatically. Read it yourself if you want that,
e.g. `new ApifyClient(Environment.GetEnvironmentVariable("APIFY_TOKEN"))`.
e.g. `new ApifyClient(Environment.GetEnvironmentVariable("APIFY_TOKEN") ?? throw new InvalidOperationException("Set APIFY_TOKEN"))`
(the null-coalescing throw keeps the call null-safe when the variable is unset).

Get your API token from the
[Apify Console → Settings → API & Integrations](https://console.apify.com/settings/integrations).
Expand All @@ -70,11 +72,12 @@ the `using` directives for whichever ones a file references:
| Namespace | What lives here |
|---|---|
| `Apify.Client` | The entry point (`ApifyClient`), `ApifyClientOptions`, `ApifyClientVersion`, and the log-redirection helper `StreamedLog`. |
| `Apify.Client.Resources` | Every resource client the entry point returns — `ActorClient`, `RunClient`, `BuildClient`, `DatasetClient`, `KeyValueStoreClient`, `RequestQueueClient`, `TaskClient`, `ScheduleClient`, `LogClient`, `UserClient`, the `…CollectionClient` types (including `NestedWebhookCollectionClient` and `WebhookDispatchCollectionClient`), etc. |
| `Apify.Client.Resources` | Every resource client the entry point returns — `ActorClient`, `RunClient`, `BuildClient`, `DatasetClient`, `KeyValueStoreClient`, `RequestQueueClient`, `TaskClient`, `ScheduleClient`, `LogClient`, `UserClient`, `ActorVersionClient`, `ActorEnvVarClient`, the `…CollectionClient` types (including `ActorVersionCollectionClient`, `ActorEnvVarCollectionClient`, `NestedWebhookCollectionClient`, and `WebhookDispatchCollectionClient`), etc. |
| `Apify.Client.Models` | Data models returned by the clients — `Actor`, `ActorRun`, `Build`, `Dataset`, `RequestQueueRequest`, `ActorEnvVar`, `PaginationList<T>`, and so on. |
| `Apify.Client.Options` | The option/request objects passed into methods — `ActorStartOptions`, `DatasetListItemsOptions`, `DownloadItemsFormat`, `ListOptions`, `SetRecordOptions`, `StorageListOptions`, etc. |
| `Apify.Client.Exceptions` | `ApifyApiException` and `ApifyTransportException`. |
| `Apify.Client.Http` | The replaceable transport: `IHttpTransport` and the default `HttpClientTransport`. |
| `System.Text.Json.Nodes` (BCL) | Not an Apify namespace, but required whenever you name the JSON escape-hatch types the client returns/accepts — `JsonObject`/`JsonNode` (e.g. dataset items, `GetInputAsync`/`UpdateInputAsync`, `GetStatisticsAsync`, `GetOpenApiDefinitionAsync`, `MonthlyUsageAsync`/`LimitsAsync`, `RequestQueueRequest.UserData`). Add `using System.Text.Json.Nodes;`. |

Fluent chains such as `client.Actor("id").Builds()` compile with only `using Apify.Client;` because the
intermediate types are inferred. You only need `using Apify.Client.Resources;` when you name a resource
Expand Down Expand Up @@ -116,11 +119,15 @@ var configured = new ApifyClient(new ApifyClientOptions
| `MaxDelayBetweenRetriesMillis` | request timeout | Upper bound on the growing inter-retry delay. |
| `TimeoutSecs` | `360` | Overall per-request timeout. |
| `UserAgentSuffix` | `null` | Custom suffix appended to the `User-Agent` header. |
| `RequestCompression` | `RequestCompression.Brotli` | Algorithm used to compress request bodies ≥ 1024 bytes: `Brotli` (`Content-Encoding: br`) or `Gzip` (`Content-Encoding: gzip`). |
| `HttpTransport` | `HttpClientTransport` | The replaceable transport (`Apify.Client.Http.IHttpTransport`). |

Requests are retried on network errors, HTTP 429 (rate limit) and 5xx responses, with exponential
backoff and jitter. 4xx responses (other than 429) are thrown immediately as `ApifyApiException`.

Request bodies of at least 1024 bytes are compressed before sending. Brotli is used by default; set
`RequestCompression = RequestCompression.Gzip` to send gzip-compressed bodies instead.

### Replaceable HTTP transport

The transport is `Apify.Client.Http.IHttpTransport`. The default is `HttpClientTransport`, which wraps
Expand Down
47 changes: 46 additions & 1 deletion docs/actors.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ foreach (var actor in page.Items)
- `DefaultBuildAsync(int? waitForFinish = null)` → `BuildClient`.
- `LastRun(LastRunOptions? options = null)` → `RunClient` (filter by `Status`/`Origin`).
- `Builds()` → `BuildCollectionClient`; `Runs()` → `RunCollectionClient`.
- `Version(string versionNumber)` / `Versions()` Actor versions.
- `Version(string versionNumber)` → `ActorVersionClient`; `Versions()` → `ActorVersionCollectionClient` — a single Actor version and the version collection.
- `Webhooks()` → read-only `NestedWebhookCollectionClient`.

`ActorStartOptions` fields:
Expand Down Expand Up @@ -87,6 +87,51 @@ against) and `ContentType` (`string?`, content type of the input; defaults to `a

## Versions and environment variables

Manage an Actor's versions with `client.Actor(id).Versions()` (the whole collection) and
`client.Actor(id).Version(versionNumber)` (one version). Each version in turn owns a collection of
environment variables, reached with `.EnvVars()` / `.EnvVar(name)`.

### Version collection — `client.Actor(id).Versions()` → `ActorVersionCollectionClient`

- `ListAsync(ListOptions? options = null)` — list the Actor's versions (one page). Returns
`PaginationList<ActorVersion>`.
- `IterateAsync(ListOptions? options = null)` → `IAsyncEnumerable<ActorVersion>` — lazily iterate every
version across pages, fetching each page on demand.
- `CreateAsync(object version)` — create a version from any JSON-serializable definition. Returns
`ActorVersion`.

`ListOptions` fields: `Offset` (`int?`, items to skip), `Limit` (`int?`, page size), `Desc` (`bool?`,
newest-first when `true`).

### Single version — `client.Actor(id).Version(versionNumber)` → `ActorVersionClient`

`versionNumber` is the version identifier (e.g. `0.1`).

- `GetAsync()` → `ActorVersion?` (null if not found).
- `UpdateAsync(object newFields)` → `ActorVersion` — update with any JSON-serializable set of fields.
- `DeleteAsync()`.
- `EnvVars()` → `ActorEnvVarCollectionClient` — this version's environment-variable collection.
- `EnvVar(string name)` → `ActorEnvVarClient` — a single environment variable of this version.

### Env-var collection — `Version(versionNumber).EnvVars()` → `ActorEnvVarCollectionClient`

- `ListAsync()` — list the version's environment variables (the endpoint returns them in a single page).
Returns `PaginationList<ActorEnvVar>`.
- `IterateAsync()` → `IAsyncEnumerable<ActorEnvVar>` — iterate the variables; provided for parity with
the other collection iterators (yields the single page's items).
- `CreateAsync(ActorEnvVar envVar)` → `ActorEnvVar` — create an environment variable.

### Single env-var — `Version(versionNumber).EnvVar(name)` → `ActorEnvVarClient`

`name` is the environment variable's name.

- `GetAsync()` → `ActorEnvVar?` (null if not found).
- `UpdateAsync(ActorEnvVar envVar)` → `ActorEnvVar`.
- `DeleteAsync()`.

See [`ActorVersion`](models.md#actorversion) and [`ActorEnvVar`](models.md#actorenvvar) for the returned
models and the `ActorEnvVar` constructor used below.

```csharp
using Apify.Client;
using Apify.Client.Models;
Expand Down
2 changes: 1 addition & 1 deletion docs/builds.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Access the account-wide build collection with `client.Builds()`, an Actor's buil
- `WaitForFinishAsync(int? waitSecs = null)` → `Build` — client-side polling until terminal (`null`
waits indefinitely).
- `GetOpenApiDefinitionAsync()` → `JsonObject?`.
- `Log()` → `LogClient`.
- `Log()` → `LogClient` (its `GetAsync`/`StreamAsync` methods are documented under [Logs in misc.md](misc.md#logs--clientlogbuildorrunid)).

Builds are created with `client.Actor(id).BuildAsync(string versionNumber, ActorBuildOptions? options = null)`.
`ActorBuildOptions` fields:
Expand Down
17 changes: 11 additions & 6 deletions docs/examples.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
# Examples

Each example below is a complete, runnable scenario. The canonical, compiled versions live in
[`tests/Apify.Client.Tests/Examples`](../tests/Apify.Client.Tests/Examples) and are executed
end-to-end against the live API by the **Test examples** CI step (they require an `APIFY_TOKEN`), so
the snippets here are guaranteed to stay valid and working.
Each example below is a complete, runnable scenario. The canonical, compiled versions live in the
client's source repository under
[`tests/Apify.Client.Tests/Examples`](https://github.com/apify/apify-client-dotnet/tree/master/tests/Apify.Client.Tests/Examples)
— a repo-internal path that is not part of the published NuGet package — and are executed end-to-end against the live API by the
**Test examples** CI step (they require an `APIFY_TOKEN`), so the snippets here are guaranteed to stay
valid and working.

Every snippet runs inside an `async` context and assumes the following `using` directives appear at the
top of the file, **before** any top-level statements (a `using` after the first statement is a `CS1529`
Expand All @@ -16,7 +18,9 @@ using Apify.Client;
using Apify.Client.Models;
using Apify.Client.Options;

var client = new ApifyClient(Environment.GetEnvironmentVariable("APIFY_TOKEN"));
var client = new ApifyClient(
Environment.GetEnvironmentVariable("APIFY_TOKEN")
?? throw new InvalidOperationException("Set the APIFY_TOKEN environment variable."));
```

## Run a store Actor and read its dataset
Expand All @@ -25,7 +29,8 @@ var client = new ApifyClient(Environment.GetEnvironmentVariable("APIFY_TOKEN"));
// The third argument bounds the wait in seconds (120 here); pass null to wait indefinitely.
var run = await client.Actor("apify/hello-world").CallAsync(null, null, 120);
var items = await client.Dataset(run.DefaultDatasetId!).ListItemsAsync(new DatasetListItemsOptions());
Console.WriteLine("Item count: " + items.Count);
// Count is the number of items in THIS page; Total is the dataset's full count across all pages.
Console.WriteLine($"Items on this page: {items.Count} (of {items.Total} total)");
```

## Each storage: create, push, read
Expand Down
10 changes: 10 additions & 0 deletions docs/misc.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ var client = new ApifyClient("my-api-token");
var me = await client.Me().GetAsync();
Console.WriteLine(me?.Username);
var usage = await client.Me().MonthlyUsageAsync();

// Any user's public profile by id (no private fields, and no account-only methods).
var otherUser = await client.User("some-user-id").GetAsync();
Console.WriteLine(otherUser?.Username);
```

## Logs — `client.Log(buildOrRunId)`
Expand All @@ -66,10 +70,16 @@ var usage = await client.Me().MonthlyUsageAsync();

```csharp
using System;
using System.IO;
using Apify.Client;
using Apify.Client.Options;

var client = new ApifyClient("my-api-token");
var log = await client.Log("some-run-id").GetAsync(new LogOptions { Raw = true });
Console.WriteLine(log);

// Read a run's raw live log as a stream (the low-level alternative to GetStreamedLog's sink redirection).
await using var logStream = await client.Run("some-run-id").GetStreamedLogAsync();
using var reader = new StreamReader(logStream);
Console.WriteLine(await reader.ReadToEndAsync());
```
2 changes: 1 addition & 1 deletion docs/runs.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ collections).
- `ChargeAsync(RunChargeOptions options)` → `Task` — record pay-per-event charges (idempotent).
- `WaitForFinishAsync(int? waitSecs = null)` → `ActorRun`.
- `Dataset()`, `KeyValueStore()`, `RequestQueue()` — the run's default storages.
- `Log()` → `LogClient`; `GetStreamedLogAsync()` → `Stream` (live raw log).
- `Log()` → `LogClient` (its `GetAsync`/`StreamAsync` methods are documented under [Logs in misc.md](misc.md#logs--clientlogbuildorrunid)); `GetStreamedLogAsync()` → `Stream` (live raw log).
- `GetStreamedLog(Action<string> toLog, bool fromStart = true)` → `StreamedLog` — redirects the run's live
log to `toLog` one complete message at a time. Call `Start()` to begin and `StopAsync()` (or dispose) to
end. `fromStart: false` skips messages older than the helper's creation.
Expand Down
11 changes: 6 additions & 5 deletions docs/storages.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ reached from a run (`client.Run(id).Dataset()`, `.KeyValueStore()`, `.RequestQue
`byte[]` (raw bytes, so binary formats like `Xlsx` are not corrupted; decode text formats yourself).
- `PushItemsAsync(object items)` — push one object or an array of objects.
- `GetStatisticsAsync()` → `JsonObject?`.
- `CreateItemsPublicUrlAsync(DatasetListItemsOptions? options = null, int? expiresInSecs = null)` → signed public URL.
- `CreateItemsPublicUrlAsync(DatasetListItemsOptions? options = null, int? expiresInSecs = null)` → `string` (a signed public URL).

`DatasetListItemsOptions` selects and reshapes items: `Offset`/`Limit` (pagination), `Desc` (reverse
order), `Fields`/`OutputFields`/`Omit` (choose columns), `Unwind`/`Flatten` (restructure nested
Expand Down Expand Up @@ -96,8 +96,8 @@ Console.WriteLine(Encoding.UTF8.GetString(csvBytes)); // CSV is text; decode the
- `SetRecordAsync(string key, byte[] value, string contentType, SetRecordOptions? options = null)` and
`SetRecordJsonAsync(string key, object? value)` (serializes `value` to JSON bytes).
- `DeleteRecordAsync(string key)`.
- `GetRecordPublicUrlAsync(string key)` and
`CreateKeysPublicUrlAsync(ListKeysOptions? options = null, int? expiresInSecs = null)`.
- `GetRecordPublicUrlAsync(string key)` → `string` and
`CreateKeysPublicUrlAsync(ListKeysOptions? options = null, int? expiresInSecs = null)` → `string` (both signed public URLs).

`ListKeysOptions` fields: `Limit` (page size), `ExclusiveStartKey` (start after this key),
`Prefix` (only keys with this prefix), `Collection` (a named record collection), and `Signature`
Expand Down Expand Up @@ -136,8 +136,9 @@ options set a stable `ClientKey` (required to manage locks the client created) a

- `GetAsync()` → `RequestQueue?`; `UpdateAsync(object newFields)` → `RequestQueue`; `DeleteAsync()`.
- `AddRequestAsync(RequestQueueRequest request, bool forefront = false)` → `RequestQueueOperationInfo`.
- `GetRequestAsync(string id)`, `UpdateRequestAsync(RequestQueueRequest request, bool forefront = false)`,
`DeleteRequestAsync(string id)`.
- `GetRequestAsync(string id)` → `RequestQueueRequest?` (null if not found);
`UpdateRequestAsync(RequestQueueRequest request, bool forefront = false)` → `RequestQueueOperationInfo`;
`DeleteRequestAsync(string id)` (no return value).
- `ListHeadAsync(int? limit = null)` → `RequestQueueHead`;
`ListAndLockHeadAsync(int lockSecs, int? limit = null)`.
- `BatchAddRequestsAsync(IReadOnlyList<RequestQueueRequest> requests, bool forefront = false, BatchAddRequestsOptions? options = null)`
Expand Down
2 changes: 1 addition & 1 deletion src/Apify.Client/Apify.Client.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<!-- NuGet package metadata (see the publish workflow). -->
<PackageId>Apify.Client</PackageId>
<Version>0.1.1</Version>
<Version>0.1.2</Version>
<Authors>Apify</Authors>
<Company>Apify</Company>
<Product>Apify API client for .NET</Product>
Expand Down
Loading
Loading