diff --git a/CHANGELOG.md b/CHANGELOG.md index 35f8cf9..1427ddd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## 0.10.0 + +- Added `RequestCompression.Zstd` and `ZstdHttpCompressor` (quality 1 to 19, default 3), which compress request + bodies with `Content-Encoding: zstd`. The client now depends on `ZstdSharp.Port`, because .NET 8 has no zstd + encoder. +- Added `ActorServerRequestOptions.ParentRequestId`, which `ActorServerClient.RequestAsync` sends as the + `X-Actor-Request-ID` header, over one passed in `Headers`. +- Documented `zstd` as an accepted `SetRecordOptions.ContentEncoding`, and that `RunChargeOptions.RequestId` is + ignored by runs of single-tenant Server Actors. +- Shortened the code comments and documentation: removed restated, outdated, and repeated text. +- Bumped `ApifyClientVersion.ApiSpecVersion` to the Apify OpenAPI spec `v2-2026-11-09T143926Z` and the project + version to `0.10.0`; regenerated the embedded response schemas. + ## 0.9.0 - Added `ActorClient.Server()`, which returns an `ActorServerClient` for Server Actors (formerly Actor @@ -162,7 +175,7 @@ bounds the retrying, after which the last error is thrown. Matches the reference client's `waitForResources` start option. - Fixed the auto-paging collection iterators (`IterateAsync` on every collection client) to stop only once - a page returns no items, instead of once `offset` reached an earlier page's `total` — a stale bound that + a page returns no items, instead of once `offset` reached an earlier page's `total`, a stale bound that cut the iteration short when the listed collection grew while it ran (e.g. a dataset collection while another process created more datasets). - Fixed `RunClient.WaitForFinishAsync`/`BuildClient.WaitForFinishAsync` to throw `NotFoundException` after @@ -186,8 +199,8 @@ `UnauthorizedException` 401, `ForbiddenException` 403, `NotFoundException` 404, `ConflictException` 409, `RateLimitException` 429, `ServerException` 5xx), matching the reference client's `ApifyApiError` subclass hierarchy. Every subclass still satisfies `catch (ApifyApiException)`. -- **Breaking:** a 404 on a resource addressed without its own id — `RunClient.Dataset()`, - `.KeyValueStore()`, `.RequestQueue()`, `.Log()`, and `BuildClient.Log()` — now throws instead of +- **Breaking:** a 404 on a resource addressed without its own id (`RunClient.Dataset()`, + `.KeyValueStore()`, `.RequestQueue()`, `.Log()`, and `BuildClient.Log()`) now throws instead of resolving to `null`/no-op, since the ambiguity (the run/build itself, or the nested resource, could be missing) made the old `null` misleading. A record/request/log looked up by its own key or id (`GetRecordAsync`, `GetRequestAsync`, `client.Log(id)`) is unaffected and still resolves `null` for @@ -233,8 +246,8 @@ short-or-medium-extended-to-cover-the-wait. `RequestQueueClientOptions.TimeoutSecs` and `SetRecordOptions.TimeoutSecs` are unaffected: both are absolute per-call/per-client overrides that already took priority over any default and still do. -- Added `WithTimeout(TimeSpan)` to every resource client (every accessor on `ApifyClient` — `Actor()`, - `Dataset()`, `Actors()`, and so on), the client's general per-call(-instance) timeout override +- Added `WithTimeout(TimeSpan)` to every resource client (every accessor on `ApifyClient`, such as `Actor()`, + `Dataset()`, and `Actors()`), the client's general per-call(-instance) timeout override matching the reference client's per-call `timeoutSecs` option: every call made through that client instance uses the given timeout instead of its tier default. A value above `ApifyClientOptions.TimeoutSecs` (the overall budget) is capped at it, matching the reference client @@ -243,7 +256,7 @@ and `UserClient.UpdateLimitsAsync` now also honor a context timeout override (previously silently ignored it, since each calls the HTTP client directly rather than through a shared CRUD primitive). - Response URL fields the OpenAPI specification marks `format: uri` (currently `ActorRun.ContainerUrl`, - `Webhook.RequestUrl` — the client's only two typed properties among the reference client's 17 + `Webhook.RequestUrl`, the client's only two typed properties among the reference client's 17 normalized fields) are now normalized to their RFC 3986 absolute-URI form via `System.Uri` (lowercased/punycoded host, default port dropped, empty path becomes `/`, unsafe characters percent-encoded), matching the reference client's `z.url({ normalize: true })`. A present but @@ -338,7 +351,7 @@ sibling clients already apply to this same resource: - 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 — + `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): @@ -396,4 +409,4 @@ sibling clients already apply to this same resource: - Integration test suite, documentation with runnable examples, a data-model property reference (`docs/models.md`), and CI workflows for integration tests and publishing (manual NuGet.org publish via Trusted Publishing: the `NuGet/login` action exchanges a GitHub OIDC token for a short-lived - key, using only the `NUGET_USER` repository secret — no long-lived NuGet API key is stored). + key, using only the `NUGET_USER` repository secret, so no long-lived NuGet API key is stored). diff --git a/README.md b/README.md index 1a9a788..004ebfc 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Apify API client for .NET -> **Official, but experimental — AI-generated and AI-maintained.** This is an official Apify client, +> **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. @@ -36,11 +36,11 @@ var run = await client.Actor("apify/hello-world").CallAsync(); // Read items from the run's default dataset. var items = await client.Dataset(run.DefaultDatasetId!).ListItemsAsync(); -// Items.Count is the number of items in THIS page; Total is the dataset's full count across all pages. +// Items.Count is the number of items on this page; Total is the count across all pages. Console.WriteLine($"Items on this page: {items.Items.Count} (of {items.Total} total)"); ``` -`new ApifyClient("my-api-token")` takes the token as an explicit argument — it does **not** read +`new ApifyClient("my-api-token")` takes the token as an explicit argument and does **not** read `APIFY_TOKEN` automatically. Get your token from the [Apify Console → Settings → API & Integrations](https://console.apify.com/settings/integrations). @@ -51,8 +51,8 @@ Full documentation lives in [`docs/`](docs/README.md), organized by resource, wi ## Versioning -- `Apify.Client.ApifyClientVersion.ClientVersion` — the semantic version of this library. -- `Apify.Client.ApifyClientVersion.ApiSpecVersion` — the Apify OpenAPI spec version this client was +- `Apify.Client.ApifyClientVersion.ClientVersion`: the semantic version of this library. +- `Apify.Client.ApifyClientVersion.ApiSpecVersion`: the Apify OpenAPI spec version this client was built against. ## License diff --git a/docs/README.md b/docs/README.md index dadb17a..0c319e0 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2,27 +2,26 @@ See the [top-level README](../README.md) for the client's official-but-experimental status. -A resource-oriented .NET client for the [Apify API](https://docs.apify.com/api/v2), mirroring the -official [JavaScript](https://github.com/apify/apify-client-js) reference client: start from an -`ApifyClient`, then drill down into resources. +A resource-oriented client for the [Apify API](https://docs.apify.com/api/v2), mirroring the +[JavaScript](https://github.com/apify/apify-client-js) reference client: start from an `ApifyClient` and +drill down into resources. -All API calls are asynchronous and return `Task`/`Task`; every method takes an optional -`CancellationToken cancellationToken = default` as its final parameter (omitted from the reference -signatures on the pages below for brevity). Method names mirror the reference client with the .NET -`Async` suffix (`GetAsync`, `ListAsync`, `CallAsync`, …). +All calls are asynchronous. Every method takes an optional `CancellationToken cancellationToken = default` as +its last parameter, omitted from the signatures in these pages. Method names follow the reference client with +the `Async` suffix (`GetAsync`, `ListAsync`, `CallAsync`, …). ## Contents -- [Actors](actors.md) — create, run, build, validate input, Server Actors, versions and environment variables. -- [Builds](builds.md) — fetch, wait, abort, logs, OpenAPI definition. -- [Runs](runs.md) — get/wait, abort, metamorph, reboot, resurrect, charge, storages, logs. -- [Storages](storages.md) — datasets, key-value stores, request queues. -- [Tasks](tasks.md) — pre-configured Actor runs. +- [Actors](actors.md): create, run, build, validate input, Server Actors, versions, and environment variables. +- [Builds](builds.md): fetch, wait, abort, logs, OpenAPI definition. +- [Runs](runs.md): get/wait, abort, metamorph, reboot, resurrect, charge, storages, logs. +- [Storages](storages.md): datasets, key-value stores, request queues. +- [Tasks](tasks.md): pre-configured Actor runs. - [Schedules](schedules.md) -- [Webhooks](webhooks.md) — webhooks and dispatches. -- [Misc](misc.md) — the Apify Store, users, logs. -- [Data models](models.md) — property reference for every returned model (`Actor`, `ActorRun`, `Build`, `PaginationList`, …). -- [Examples](examples.md) — runnable end-to-end examples. +- [Webhooks](webhooks.md): webhooks and dispatches. +- [Misc](misc.md): the Apify Store, users, logs. +- [Data models](models.md): property reference for every returned model (`Actor`, `ActorRun`, `Build`, `PaginationList`, …). +- [Examples](examples.md): runnable end-to-end examples. ## Requirements @@ -36,8 +35,7 @@ dotnet add package Apify.Client ## Quick start -All snippets in this documentation assume `ImplicitUsings` is disabled (the repository's convention), -so every `using` — even `System` — is listed explicitly and appears before any top-level statement. +The snippets assume `ImplicitUsings` is disabled, so every `using`, `System` included, is listed. ```csharp using System; @@ -45,42 +43,33 @@ using Apify.Client; var client = new ApifyClient("my-api-token"); -// Start an Actor and wait for it to finish. Pass waitSecs (e.g. 120) to bound the wait; -// by default it waits indefinitely. +// Start an Actor and wait for it to finish. Pass waitSecs to bound the wait. var run = await client.Actor("apify/hello-world").CallAsync(); // Read items from the run's default dataset. var items = await client.Dataset(run.DefaultDatasetId!).ListItemsAsync(); -// Items.Count is the number of items in THIS page; Total is the dataset's full count across all pages. +// Items.Count is the number of items on this page; Total is the count across all pages. Console.WriteLine($"Items on this page: {items.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") ?? 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). +The client does not read `APIFY_TOKEN` or any other environment variable; pass the token yourself, for example +`new ApifyClient(Environment.GetEnvironmentVariable("APIFY_TOKEN") ?? throw new InvalidOperationException("Set APIFY_TOKEN"))`. +Get a token in [Apify Console → Settings → API & Integrations](https://console.apify.com/settings/integrations). ## Namespaces -The public API is spread across a small set of namespaces. Because `ImplicitUsings` is disabled, add -the `using` directives for whichever ones a file references: - -| Namespace | What lives here | +| Namespace | Contents | |---|---| -| `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`, `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`, and so on. | -| `Apify.Client.Options` | The option/request objects passed into methods — `ActorStartOptions`, `ActorBuildOptions`, `ActorListOptions`, `RunListOptions`, `RunResurrectOptions`, `RunChargeOptions`, `MetamorphOptions`, `LastRunOptions`, `DatasetListItemsOptions`, `DatasetDownloadOptions`, `DownloadItemsFormat`, `GetRecordOptions`, `SetRecordOptions`, `ListKeysOptions`, `IterateKeysOptions`, `ListRequestsOptions`, `PaginateRequestsOptions`, `BatchAddRequestsOptions`, `RequestQueueClientOptions`, `TaskStartOptions`, `ValidateInputOptions`, `LogOptions`, `ListOptions`, `StorageListOptions`, `StoreListOptions`. | -| `Apify.Client.Exceptions` | `ApifyClientException` (common base), `ApifyApiException` (and its per-status subclasses), `ApifyTransportException`, `InvalidResponseBodyException`, `ResponseValidationException` and `ActorServerException`. | -| `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 -client type explicitly — e.g. storing one in a variable or field: +| `Apify.Client` | `ApifyClient`, `ApifyClientOptions`, `ApifyClientVersion`, `RequestCompression`, `Statistics`, and `StreamedLog`. | +| `Apify.Client.Resources` | The resource clients the entry point returns (`ActorClient`, `RunClient`, `DatasetClient`, the `…CollectionClient` types, …). | +| `Apify.Client.Models` | The models the clients return (`Actor`, `ActorRun`, `Build`, `PaginationList`, …). | +| `Apify.Client.Options` | The option objects passed to methods (`ActorStartOptions`, `ListOptions`, `SetRecordOptions`, …). | +| `Apify.Client.Exceptions` | `ApifyClientException` and its subclasses. | +| `Apify.Client.Http` | `IHttpTransport`, `HttpClientTransport`, and the request body compressors. | +| `System.Text.Json.Nodes` | Not an Apify namespace, but needed to name `JsonObject` and `JsonNode`, which the clients use for dataset items, inputs, and other free-form data. | + +Fluent chains such as `client.Actor("id").Builds()` need only `using Apify.Client;`. Add +`using Apify.Client.Resources;` to name a resource client type, for example in a variable: ```csharp using Apify.Client; @@ -111,60 +100,51 @@ var configured = new ApifyClient(new ApifyClientOptions | Option | Default | Meaning | |---|---|---| | `Token` | `null` | API token, sent as a Bearer token. | -| `BaseUrl` | `https://api.apify.com` | API base URL, with or without the `/v2` version path — it's appended only when not already present. | -| `PublicBaseUrl` | `https://api.apify.com` | Base URL used when building public, shareable resource URLs, whatever `BaseUrl` is (as in the reference client). Also accepted with or without `/v2`. | +| `BaseUrl` | `https://api.apify.com` | API base URL, with or without `/v2`. | +| `PublicBaseUrl` | `https://api.apify.com` | Base URL of public, shareable resource URLs, whatever `BaseUrl` is, as in the reference client. | | `MaxRetries` | `8` | Maximum retries for failed requests. | | `MinDelayBetweenRetriesMillis` | `500` | Minimum delay between retries (exponential backoff). | -| `MaxDelayBetweenRetriesMillis` | request timeout | Upper bound on the growing inter-retry delay. | -| `TimeoutSecs` | `360` | The overall per-request cap (the reference client's `timeoutMaxSecs`): every attempt's growing timeout, including a `WithTimeout` override, is clamped to it, regardless of tier. Also the base of the `Long` tier unless `TimeoutLongSecs` is set. | -| `TimeoutLongSecs` | `TimeoutSecs` | Base duration of the `Long` timeout tier: downloads, uploads, streaming. Matches the reference client's `timeoutLongSecs`. | -| `TimeoutShortSecs` | `5` | Base duration of the `Short` timeout tier: simple metadata reads/writes (get/update/delete a resource). | -| `TimeoutMediumSecs` | `30` | Base duration of the `Medium` timeout tier: listing, batch and trigger calls. | +| `MaxDelayBetweenRetriesMillis` | request timeout | Upper bound of the growing delay between retries. | +| `TimeoutSecs` | `360` | The overall per-request cap (the reference client's `timeoutMaxSecs`): every attempt's timeout, a `WithTimeout` override included, is clamped to it. Also the base of the `Long` tier unless `TimeoutLongSecs` is set. | +| `TimeoutLongSecs` | `TimeoutSecs` | Base duration of the `Long` tier: downloads, uploads, and streaming. | +| `TimeoutShortSecs` | `5` | Base duration of the `Short` tier: metadata reads and writes. | +| `TimeoutMediumSecs` | `30` | Base duration of the `Medium` tier: listing, batch, and trigger calls. | | `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`). | -| `Compressor` | `null` | A custom `HttpCompressor` for a non-default compression quality or another algorithm; takes precedence over `RequestCompression`. | -| `Headers` | `null` | Additional headers sent with every request, such as an integration platform marker. They win over the built-in `User-Agent` and `Authorization`; header names are case-insensitive, and `client.UserAgent` reports a `User-Agent` override. | +| `RequestCompression` | `RequestCompression.Brotli` | Algorithm used to compress request bodies ≥ 1024 bytes: `Brotli` (`Content-Encoding: br`), `Gzip` (`Content-Encoding: gzip`) or `Zstd` (`Content-Encoding: zstd`). | +| `Compressor` | `null` | A custom `HttpCompressor` for another quality or algorithm; takes precedence over `RequestCompression`. | +| `Headers` | `null` | Headers sent with every request, such as an integration marker. They win over the built-in `User-Agent` and `Authorization`; names are case-insensitive. A content header such as `Content-Type` makes the constructor throw. | | `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`. +Requests are retried with exponential backoff and jitter on network errors, 429, and 5xx responses. Other 4xx +responses throw `ApifyApiException` at once. ### Request statistics -`client.Stats` counts what the client has done, as the reference client's `stats` does: `Calls` (client calls), -`Requests` (HTTP requests, including retries) and `RateLimitErrors` (a snapshot list of the `429` answers per -attempt: index 0 holds the first-attempt errors, index 1 the first retry's, and so on). +`client.Stats` counts `Calls`, `Requests` (including retries) and `RateLimitErrors` (the `429` answers per attempt: +index 0 is the first attempt, index 1 the first retry, and so on), as the reference client's `stats` does. ### Timeout tiers -Each method internally picks the timeout tier that fits the expected duration of its request — `Short` -for metadata reads and writes, `Medium` for listing/batch/trigger calls, `Long` for downloads, uploads -and streaming — so the default wait before giving up is consistent across the client instead of a -single one-size-fits-all budget. The three tiers' durations are configured once, on the options above -(`TimeoutShortSecs`/`TimeoutMediumSecs`/`TimeoutLongSecs`); `TimeoutSecs` is the cap that none of them can exceed. - -Every resource client (`client.Actor(id)`, `client.Dataset(id)`, `client.Actors()`, and so on — the -return value of every accessor on `ApifyClient`) exposes `WithTimeout(TimeSpan timeout)`, a general -per-call(-instance) override: call it right after the accessor to make every call made through that -client instance use `timeout` instead of its tier default, for exactly one use of that client, e.g. -`client.Dataset(id).WithTimeout(TimeSpan.FromMinutes(5)).DownloadItemsAsync(...)`. A value above -`TimeoutSecs` (the overall budget) is capped at it, matching the reference client — raise `TimeoutSecs` -itself for a longer per-call timeout; a shorter override still narrows the cap, as before. Pass -`TimeSpan.Zero` for no timeout at all, matching the reference client's `'noTimeout'`. `RequestQueueClient` -(via `client.RequestQueue(id, new RequestQueueClientOptions { -TimeoutSecs = ... })`) and `SetRecordAsync` (via `SetRecordOptions.TimeoutSecs`) already had their own -equivalent, narrower overrides before `WithTimeout` existed and keep them instead. +Each method picks the timeout tier that fits its request: `Short` for metadata reads and writes, `Medium` for +listing, batch, and trigger calls, `Long` for downloads, uploads, and streaming. The tier durations are set by +`TimeoutShortSecs`, `TimeoutMediumSecs`, and `TimeoutLongSecs`; `TimeoutSecs` is the cap none can exceed. + +Every resource client has `WithTimeout(TimeSpan timeout)`, which overrides the tier default for the calls made +through that client instance, for example `client.Dataset(id).WithTimeout(TimeSpan.FromMinutes(5)).DownloadItemsAsync(...)`. +A value above `TimeoutSecs` is capped at it, as in the reference client, and `TimeSpan.Zero` means no timeout. +`RequestQueueClient` (`RequestQueueClientOptions.TimeoutSecs`) and `SetRecordAsync` (`SetRecordOptions.TimeoutSecs`) +have their own narrower overrides. ### Request body compression -Request bodies of at least 1024 bytes are compressed before sending, unless their content type already -carries its own compression (images, audio, video, archives). Brotli is used by default; set -`RequestCompression = RequestCompression.Gzip` to send gzip-compressed bodies instead. +Bodies of at least 1024 bytes are compressed before sending, unless their content type already carries its own +compression (images, audio, video, archives). Brotli is the default; set `RequestCompression` to `Gzip` or `Zstd` +to use those. -For a different quality, pass a compressor: `BrotliHttpCompressor` takes a quality from 0 (the fastest) to 11 -(the best compression, default 6) and `GzipHttpCompressor` a `System.IO.Compression.CompressionLevel` -(`Fastest`, `Optimal` or `SmallestSize`; default `Optimal`). Derive from `HttpCompressor` to use another -algorithm the API accepts (`deflate`, `identity`): set `ContentEncoding` and implement `CompressAsync`. +For another quality, pass a compressor: `BrotliHttpCompressor` takes a quality from 0 to 11 (default 6), +`GzipHttpCompressor` a `System.IO.Compression.CompressionLevel` (default `Optimal`) and `ZstdHttpCompressor` a +quality from 1 to 19 (default 3). To use another encoding the API accepts (`deflate`, `identity`), derive from +`HttpCompressor`: set `ContentEncoding` and implement `CompressAsync`. ```csharp using Apify.Client; @@ -177,18 +157,10 @@ var client = new ApifyClient(new ApifyClientOptions }); ``` -### Default headers - -`ApifyClientOptions.Headers` adds headers to every request, for example an integration platform marker. They -replace the built-in `User-Agent` and `Authorization` headers when named the same, compared -case-insensitively. Only request headers are allowed; a content header such as `Content-Type` makes the -`ApifyClient` constructor throw, because the client sets those per request. - ### Replaceable HTTP transport -The transport is `Apify.Client.Http.IHttpTransport`. The default is `HttpClientTransport`, which wraps -`System.Net.Http.HttpClient`; you can pass a pre-configured `HttpClient` (proxy, TLS, connection pool) -or provide your own `IHttpTransport` (e.g. a mock in tests): +The default `HttpClientTransport` wraps `HttpClient`. Pass a configured `HttpClient` (proxy, TLS, connection +pool) or your own `IHttpTransport`, for example a mock in tests: ```csharp using System.Net.Http; @@ -203,20 +175,18 @@ var client = new ApifyClient(new ApifyClientOptions }); ``` -Requests to an Actor's server (`client.Actor(id).Server()`, see [Actors](actors.md)) must not follow -redirects, because they carry the API token in a header. They set -`Apify.Client.Http.ApifyRequestOptions.FollowRedirects` to `false` in `HttpRequestMessage.Options`, and a -custom `IHttpTransport` has to return a redirect response as it is for such a request. `HttpClientTransport` -does this with a second `HttpClient` that has automatic redirects disabled: it creates one itself, but a -transport wrapping your own `HttpClient` needs one too, passed as the second constructor argument +Requests to an Actor's server (`client.Actor(id).Server()`, see [Actors](actors.md)) carry the API token in a +header, so they must not follow redirects. They set `ApifyRequestOptions.FollowRedirects` to `false` in +`HttpRequestMessage.Options`, and a custom `IHttpTransport` has to return a redirect response as it is. +`HttpClientTransport` sends them through a second `HttpClient` with automatic redirects disabled. It creates one +itself; a transport wrapping your own `HttpClient` needs one passed as the second constructor argument (`new HttpClientTransport(httpClient, noRedirectHttpClient)`), otherwise such a request throws `InvalidOperationException`. ## Error handling -Methods that fetch a single resource return `null` when the resource does not exist (rather than -throwing). Other API failures are thrown as `Apify.Client.Exceptions.ApifyApiException`, which exposes -the HTTP status, API error `Type`, message, attempt count, and request method/path: +Methods that fetch a single resource return `null` when it does not exist. Other API failures throw +`ApifyApiException`: ```csharp using System; @@ -240,28 +210,23 @@ catch (ApifyApiException e) |---|---|---| | `StatusCode` | `int` | HTTP status code of the error response. | | `Type` | `string?` | Machine-readable API error type (e.g. `record-not-found`). | -| `ApiMessage` | `string` | Raw error message from the API, without the status/type prefix. | +| `ApiMessage` | `string` | Error message from the API, without the status and type prefix. | | `Attempt` | `int` | 1-based number of the API-call attempt that produced the error. | | `HttpMethod` | `string` | HTTP method of the failed call (e.g. `GET`, `POST`). | | `Path` | `string` | API endpoint path (URL excluding origin). | | `ErrorData` | `JsonObject?` | Additional structured error data from the API, if any. | -`ApifyTransportException` is thrown instead when the request never reaches the API (network failure, -timeout, or DNS error) after all retries are exhausted. +The exception types, all deriving from `ApifyClientException` (so `catch (ApifyClientException)` catches them all): -`InvalidResponseBodyException` is thrown when a successful response's body is not valid JSON, and -`ResponseValidationException` when it is valid JSON but does not match the schema the API specification -declares for that operation (for example the `data` envelope or a required property is missing, or a -property has the wrong JSON type). Unknown properties and enum values are accepted, so new API fields do -not break the client. The response arrived in full, so neither is -retried. Both expose `StatusCode`, `HttpMethod`, `Path` and the raw `Body`. An error status with an -unparsable body throws the `ApifyApiException` for its status code, with the raw body in its message. +- `ApifyApiException`: the API answered with an error status. Per-status subclasses (`NotFoundException`, `RateLimitException`, …) let you catch one kind. +- `ApifyTransportException`: no response (network failure, timeout, DNS) after all retries. +- `InvalidResponseBodyException`: a successful response whose body is not JSON. +- `ResponseValidationException`: a successful response whose JSON does not match the specification's schema for the operation (a missing `data` envelope or required property, a wrong type). Unknown properties and enum values are accepted, so new API fields do not break the client. +- `ActorServerException`: an Actor that is not a Server Actor (see [Actors](actors.md)). -`ApifyApiException`, `ApifyTransportException`, `InvalidResponseBodyException`, -`ResponseValidationException` and `ActorServerException` (an Actor that is not a Server Actor, see -[Actors](actors.md)) extend the common -`ApifyClientException`, so `catch (ApifyClientException)` catches every error this client raises for a -failed call without naming the concrete types: +`InvalidResponseBodyException` and `ResponseValidationException` are not retried and expose `StatusCode`, `HttpMethod`, +`Path` and the raw `Body`. An error status with an unparsable body throws the `ApifyApiException` of its status, with +the body in its message. ```csharp using System; @@ -282,8 +247,8 @@ catch (ApifyClientException e) ## Versioning -- `Apify.Client.ApifyClientVersion.ClientVersion` — the semantic version of this library. -- `Apify.Client.ApifyClientVersion.ApiSpecVersion` — the Apify OpenAPI spec version this client was +- `Apify.Client.ApifyClientVersion.ClientVersion`: the semantic version of this library. +- `Apify.Client.ApifyClientVersion.ApiSpecVersion`: the Apify OpenAPI spec version this client was built against. ```csharp diff --git a/docs/actors.md b/docs/actors.md index 7997d75..e2ed340 100644 --- a/docs/actors.md +++ b/docs/actors.md @@ -3,13 +3,13 @@ Access the Actor collection with `client.Actors()` and a specific Actor with `client.Actor(id)`, where `id` is the Actor ID or the `username~name` form. -## Collection — `client.Actors()` +## Collection: `client.Actors()` -- `ListAsync(ActorListOptions? options = null)` — list the account's Actors (one page). Returns +- `ListAsync(ActorListOptions? options = null)`: list the account's Actors (one page). Returns `PaginationList`. -- `IterateAsync(ActorListOptions? options = null)` → `IAsyncEnumerable` — lazily iterate every +- `IterateAsync(ActorListOptions? options = null)` → `IAsyncEnumerable`: lazily iterate every Actor across pages, fetching each page on demand. -- `CreateAsync(object actor)` — create an Actor from any JSON-serializable definition. Returns `Actor`. +- `CreateAsync(object actor)`: create an Actor from any JSON-serializable definition. Returns `Actor`. `ActorListOptions` fields: @@ -35,31 +35,29 @@ foreach (var actor in page.Items) } ``` -## Single Actor — `client.Actor(id)` +## Single Actor: `client.Actor(id)` - `GetAsync()` → `Actor?` (null if not found). - `UpdateAsync(object newFields)` → `Actor`. - `DeleteAsync()`. - `StartAsync(object? input = null, ActorStartOptions? options = null)` → `ActorRun` (returns immediately). - `CallAsync(object? input = null, ActorStartOptions? options = null, int? waitSecs = null, Action? log = null)` - → `ActorRun` (starts then waits; `waitSecs` bounds the wait, `null` waits indefinitely; `log`, if set, - redirects the run's live log to that sink for the duration of the wait; unlike the reference client, - omitting it does not redirect the log, so pass `StreamedLog.DefaultConsole` to get the reference's - default redirection to the console, with its ` runId: -> ` prefix). Time spent retrying - `ActorStartOptions.WaitForResources` does not count toward `waitSecs`. + → `ActorRun` (starts, then waits). `waitSecs` bounds the wait (`null` waits indefinitely) and does not count the + time spent retrying `WaitForResources`. `log` redirects the run's live log to that sink while waiting. Unlike + the reference client, omitting it redirects nothing; pass `StreamedLog.DefaultConsole` for the reference's + console redirection with the ` runId: -> ` prefix. - `ValidateInputAsync(object? input = null, ValidateInputOptions? options = null)` → `bool`. -`input` on `StartAsync`/`CallAsync`/`ValidateInputAsync` (and `RunClient.MetamorphAsync`, see -[Runs](runs.md)) is a plain object or array serialized to JSON, or raw bytes (`byte[]`) sent as-is — -pass `byte[]` together with `ActorStartOptions.ContentType` to send a pre-serialized or binary body -without re-encoding it as JSON. +`input` of `StartAsync`, `CallAsync`, `ValidateInputAsync`, and `RunClient.MetamorphAsync` (see [Runs](runs.md)) is +serialized to JSON, except `byte[]`, which is sent as it is. Pair `byte[]` with `ActorStartOptions.ContentType` to +send a pre-serialized or binary body. - `BuildAsync(string versionNumber, ActorBuildOptions? options = null)` → `Build`. - `DefaultBuildAsync(int? waitForFinish = null)` → `BuildClient`. - `LastRun(LastRunOptions? options = null)` → `RunClient` (filter by `Status`/`Origin`). - `Builds()` → `BuildCollectionClient`; `Runs()` → `RunCollectionClient`. -- `Version(string versionNumber)` → `ActorVersionClient`; `Versions()` → `ActorVersionCollectionClient` — a single Actor version and the version collection. +- `Version(string versionNumber)` → `ActorVersionClient`; `Versions()` → `ActorVersionCollectionClient`: a single Actor version and the version collection. - `Webhooks()` → read-only `NestedWebhookCollectionClient`. -- `Server()` → `ActorServerClient` — the Actor's server interface, see [Server Actors](#server-actors--clientactoridserver). +- `Server()` → `ActorServerClient`: the Actor's server interface, see [Server Actors](#server-actors-clientactoridserver). `ActorStartOptions` fields: @@ -73,7 +71,7 @@ without re-encoding it as JSON. | `MaxTotalChargeUsd` | `double?` | Maximum total USD the run is allowed to charge. | | `ContentType` | `string?` | Content type of the `input` body (defaults to `application/json`). | | `RestartOnError` | `bool?` | Automatically restart the run's container if it exits with an error. | -| `WaitForResources` | `WaitForResources?` | Retry the start while the account has no free memory or concurrent-run slot for it, instead of throwing immediately. `WaitForResources.UntilAvailable` retries until the run starts; `WaitForResources.For(seconds)` bounds the retrying, after which the last error is thrown. `null` (the default) disables retrying. | +| `WaitForResources` | `WaitForResources?` | Retry the start while the account has no free memory or concurrent-run slot, instead of throwing. `WaitForResources.UntilAvailable` retries until the run starts; `WaitForResources.For(seconds)` bounds the retrying, then throws the last error. `null` (the default) does not retry. | | `ForcePermissionLevel` | `string?` | Override the Actor's permission level (`LIMITED_PERMISSIONS`/`FULL_PERMISSIONS`). | | `Webhooks` | `object?` | Ad-hoc webhooks (any JSON-serializable list) to attach to this run. | @@ -96,21 +94,19 @@ against) and `ContentType` (`string?`, content type of the input; defaults to `a `LastRunOptions` fields: `Status` (`string?`, only consider the last run with this status, e.g. `SUCCEEDED`) and `Origin` (`string?`, only consider the last run started from this origin, e.g. `API`). -## Server Actors — `client.Actor(id).Server()` +## Server Actors: `client.Actor(id).Server()` -A Server Actor (formerly Actor Standby) runs a web server that is called over HTTP without starting a run -for each call. `Server()` returns an `ActorServerClient`; every call returns the same client, so the server -URL it fetches is reused. +A Server Actor (formerly Actor Standby) runs a web server that is called over HTTP without starting a run per +call. `Server()` returns an `ActorServerClient`; every call returns the same client, which reuses the server URL. -- `RequestAsync(string path = "/", ActorServerRequestOptions? options = null)` → `ActorServerResponse` — +- `RequestAsync(string path = "/", ActorServerRequestOptions? options = null)` → `ActorServerResponse`: sends a request to the Actor's server and returns its response, whatever its status. -- `GetUrlAsync()` → `string` — the server URL (the Actor's `standbyUrl`), to call the server with another - HTTP client. Fetched from the API on the first call of `GetUrlAsync`, `GetOpenApiSpecAsync` or - `RequestAsync`, then reused. -- `GetOpenApiSpecAsync()` → `JsonObject?` — the web server schema (OpenAPI specification) of the build that +- `GetUrlAsync()` → `string`: the server URL (the Actor's `standbyUrl`), to call the server with another HTTP + client. The first call of `GetUrlAsync`, `GetOpenApiSpecAsync`, or `RequestAsync` fetches it, then it is reused. +- `GetOpenApiSpecAsync()` → `JsonObject?`: the web server schema (OpenAPI specification) of the build that serves the server: `actorStandby.build` (a tag or a build number) or `latest`; `null` if the definition has none. -- `WithTimeout(TimeSpan timeout)` — the timeout of the API calls made by `GetUrlAsync` and +- `WithTimeout(TimeSpan timeout)`: the timeout of the API calls made by `GetUrlAsync` and `GetOpenApiSpecAsync`. They throw `ActorServerException` when the Actor is not a Server Actor (or the serving build does not exist), @@ -121,16 +117,15 @@ second one that does not follow redirects (see `RequestAsync` details: -- The request goes to the server URL, and `path` is always resolved against it (a fragment is dropped), so - neither the request nor the token can reach another host. -- The token travels in the `X-Apify-Authorization` header, which the platform removes before the request - reaches the Actor's code. Headers you pass, `Authorization` included, reach the Actor as they are. -- Any response is returned, error statuses included. Only 429 and 503 are retried (with the client's retry - settings, returning the last response when they run out), because other statuses can come from the Actor's - code. Network errors and timeouts are not retried, since the Actor may have already handled the request, and - a redirect is returned as it is instead of followed. -- Bodies are sent uncompressed unless `Compressor` is set. The timeout defaults to the `Long` tier, which - covers the cold start of an idle Actor. +- `path` is resolved against the server URL (a fragment is dropped), so neither the request nor the token can + reach another host. +- The token travels in the `X-Apify-Authorization` header, which the platform removes before the request reaches + the Actor's code. Headers you pass, `Authorization` included, reach the Actor as they are. +- Any response is returned, error statuses included. Only 429 and 503 are retried (the last response is returned + when the retries run out), because other statuses can come from the Actor's code. Network errors and timeouts + are not retried, since the Actor may have handled the request, and a redirect is returned instead of followed. +- Bodies are sent uncompressed unless `Compressor` is set. The timeout defaults to the `Long` tier, which covers + the cold start of an idle Actor. `ActorServerRequestOptions` fields: @@ -141,6 +136,7 @@ second one that does not follow redirects (see | `Headers` | `IReadOnlyDictionary?` | Additional request headers. A `Content-Type` header equals `ContentType`; a `Content-Encoding` header marks a body that is already encoded and is then not compressed; other `Content-*` headers (e.g. `Content-Disposition`) are sent only with a `Body`. | | `Body` | `object?` | Request body: `byte[]` and `string` are sent as they are, anything else is serialized to JSON. | | `ContentType` | `string?` | Content type of the body; defaults to `application/json`, `text/plain; charset=utf-8` for a string and `application/octet-stream` for bytes. | +| `ParentRequestId` | `string?` | The `X-Actor-Request-ID` header of the request your multi-tenant Server Actor is handling, sent on as the same header so the called Actor charges at that request's pricing tier. Replaces an `X-Actor-Request-ID` in `Headers`. Use a client created with the run's `APIFY_TOKEN`. | | `Compressor` | `HttpCompressor?` | Compressor of the body (e.g. `new GzipHttpCompressor()`). Set it only if the Actor's server decodes the resulting `Content-Encoding`. | | `StreamResponse` | `bool` | Return the response body unread, to read it with `ReadAsStreamAsync`. The response holds its connection until disposed. | | `Timeout` | `TimeSpan?` | Timeout of the request; defaults to the `Long` tier. | @@ -170,42 +166,74 @@ Console.WriteLine(await server.GetUrlAsync()); Console.WriteLine(await server.GetOpenApiSpecAsync()); ``` +### Calling a Server Actor from a Server Actor + +The platform tells the requests of a multi-tenant Server Actor apart by an ID it sends in the `X-Actor-Request-ID` +header of each request it forwards. The Actor uses it to charge the caller of the request, and to pass the caller's +pricing tier on to the Actors it calls. When your multi-tenant Server Actor calls another one while handling a +request, pass the incoming header value as `ParentRequestId`. The client sends it as the same header, so the called +Actor charges at the pricing tier of your incoming request. Use a client created with the run's `APIFY_TOKEN`. See +[Call another multi-tenant Server Actor](https://docs.apify.com/platform/actors/monetize/server-actors#call-another-multi-tenant-server-actor). + +```csharp +using System; +using System.Collections.Generic; +using Apify.Client; +using Apify.Client.Options; + +var client = new ApifyClient( + Environment.GetEnvironmentVariable("APIFY_TOKEN") + ?? throw new InvalidOperationException("Set the APIFY_TOKEN environment variable.")); + +// The X-Actor-Request-ID header of the request your Server Actor is handling. +var incomingRequestId = "7Hs2kQw9LxP4mN1vB"; + +using var response = await client.Actor("username/other-actor").Server().RequestAsync( + "/search", + new ActorServerRequestOptions + { + Query = new Dictionary { ["q"] = "apify" }, + ParentRequestId = incomingRequestId, + }); +Console.WriteLine(response.StatusCode); +``` + ## 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` +### Version collection: `client.Actor(id).Versions()` → `ActorVersionCollectionClient` The endpoint returns every version in one response and does not support pagination, so unlike most collection clients these methods take no filtering/paging options. -- `ListAsync()` — list the Actor's versions. Returns `PaginationList`. -- `IterateAsync()` → `IAsyncEnumerable` — iterate the versions; exists for API parity +- `ListAsync()`: list the Actor's versions. Returns `PaginationList`. +- `IterateAsync()` → `IAsyncEnumerable`: iterate the versions; exists for API parity with the other collection iterators (the endpoint has only the one page). -- `CreateAsync(object version)` — create a version from any JSON-serializable definition. Returns +- `CreateAsync(object version)`: create a version from any JSON-serializable definition. Returns `ActorVersion`. -### Single version — `client.Actor(id).Version(versionNumber)` → `ActorVersionClient` +### 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. +- `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. +- `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` +### Env-var collection: `Version(versionNumber).EnvVars()` → `ActorEnvVarCollectionClient` -- `ListAsync()` — list the version's environment variables (the endpoint returns them in a single page). +- `ListAsync()`: list the version's environment variables (the endpoint returns them in a single page). Returns `PaginationList`. -- `IterateAsync()` → `IAsyncEnumerable` — iterate the variables; provided for parity with +- `IterateAsync()` → `IAsyncEnumerable`: 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. +- `CreateAsync(ActorEnvVar envVar)` → `ActorEnvVar`: create an environment variable. -### Single env-var — `Version(versionNumber).EnvVar(name)` → `ActorEnvVarClient` +### Single env-var: `Version(versionNumber).EnvVar(name)` → `ActorEnvVarClient` `name` is the environment variable's name. diff --git a/docs/builds.md b/docs/builds.md index 33fb81f..166f7c1 100644 --- a/docs/builds.md +++ b/docs/builds.md @@ -8,17 +8,17 @@ Access the account-wide build collection with `client.Builds()`, an Actor's buil - `ListAsync(ListOptions? options = null)` → `PaginationList` (`Offset`, `Limit`, `Desc`; `ChunkSize` sizes the pages of `IterateAsync`). - `IterateAsync(ListOptions? options = null)` → `IAsyncEnumerable` (lazy, all pages). -## Single build — `client.Build(buildId)` +## Single build: `client.Build(buildId)` -- `GetAsync(int? waitForFinishSecs = null)` → `Build?` — optionally waits up to `waitForFinishSecs` +- `GetAsync(int? waitForFinishSecs = null)` → `Build?`: optionally waits up to `waitForFinishSecs` (server-side, max 60) for the build to finish. - `AbortAsync()` → `Build`. - `DeleteAsync()`. -- `WaitForFinishAsync(int? waitSecs = null)` → `Build` — client-side polling until terminal (`null` +- `WaitForFinishAsync(int? waitSecs = null)` → `Build`: client-side polling until terminal (`null` waits indefinitely). A build id that keeps answering 404 for 3 seconds straight (replica lag resolves faster than that) throws `NotFoundException` instead of polling for the rest of `waitSecs`. - `GetOpenApiDefinitionAsync()` → `JsonObject?`. -- `Log()` → `LogClient` (its `GetAsync`/`StreamAsync` methods are documented under [Logs in misc.md](misc.md#logs--clientlogbuildorrunid); on this build-nested client they throw on a 404 rather than resolve `null`, since the build itself could be what's missing). +- `Log()` → `LogClient` (its `GetAsync`/`StreamAsync` methods are documented under [Logs in misc.md](misc.md#logs-clientlogbuildorrunid); on this build-nested client they throw on a 404 rather than resolve `null`, since the build itself could be what's missing). Builds are created with `client.Actor(id).BuildAsync(string versionNumber, ActorBuildOptions? options = null)`. `ActorBuildOptions` fields: diff --git a/docs/examples.md b/docs/examples.md index 3b0be00..8802df8 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -1,15 +1,11 @@ # Examples -Each example below is a complete, runnable scenario. The canonical, compiled versions live in the -client's source repository under +Each example is a complete scenario. Compiled versions live in the 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. +and run against the live API in the **Test examples** CI step, so they stay working. They need an `APIFY_TOKEN`. -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` -compile error). `ImplicitUsings` is disabled in this repository, so even `System` is listed explicitly: +Every snippet runs in an `async` context after these directives, which must come before any top-level statement. +`System` is listed because `ImplicitUsings` is disabled: ```csharp using System; @@ -26,10 +22,10 @@ var client = new ApifyClient( ## Run a store Actor and read its dataset ```csharp -// The third argument bounds the wait in seconds (120 here); pass null to wait indefinitely. +// The third argument bounds the wait in seconds; null waits indefinitely. var run = await client.Actor("apify/hello-world").CallAsync(null, null, 120); var items = await client.Dataset(run.DefaultDatasetId!).ListItemsAsync(new DatasetListItemsOptions()); -// Items.Count is the number of items in THIS page; Total is the dataset's full count across all pages. +// Items.Count is the number of items on this page; Total is the count across all pages. Console.WriteLine($"Items on this page: {items.Items.Count} (of {items.Total} total)"); ``` diff --git a/docs/misc.md b/docs/misc.md index 9bfff8c..644a591 100644 --- a/docs/misc.md +++ b/docs/misc.md @@ -3,7 +3,7 @@ > Snippets below run inside an `async` context. `ImplicitUsings` is disabled in this repository, so all > `using` directives (including `System`) are shown explicitly and must precede any statements. -## Apify Store — `client.Store()` +## Apify Store: `client.Store()` Browse public Actors in the [Apify Store](https://apify.com/store). @@ -40,7 +40,7 @@ await foreach (var item in client.Store().IterateAsync(new StoreListOptions { Se } ``` -## Users — `client.Me()` / `client.User(id)` +## Users: `client.Me()` / `client.User(id)` - `GetAsync()` → `User?`. For `Me()` the raw payload includes private account details (`ToJsonObject()`); for `User(id)` it returns the public profile. @@ -67,18 +67,17 @@ var otherUser = await client.User("some-user-id").GetAsync(); Console.WriteLine(otherUser?.Username); ``` -## Logs — `client.Log(buildOrRunId)` +## Logs: `client.Log(buildOrRunId)` - `GetAsync(LogOptions? options = null)` → `string?` (buffered; `null` if the log does not exist). -- `GetBytesAsync(LogOptions? options = null)` → `byte[]?` — the same buffered fetch as `GetAsync`, as raw +- `GetBytesAsync(LogOptions? options = null)` → `byte[]?`: the same buffered fetch as `GetAsync`, as raw bytes instead of a decoded string (for a log that is not necessarily valid UTF-8 text). - `StreamAsync(LogOptions? options = null)` → `Stream?` (live; `null` if the log does not exist). Also `client.Run(id).GetStreamedLogAsync()`. Addressed by its own id (`client.Log(buildOrRunId)`) as above, a missing log resolves to `null`. Reached -through a run or build instead (`client.Run(id).Log()`, `client.Build(id).Log()`), a 404 is ambiguous — -it could mean the run/build itself or its log is missing — so `GetAsync`/`GetBytesAsync` throw an -`ApifyApiException` there instead. +through a run or build instead (`client.Run(id).Log()`, `client.Build(id).Log()`), a 404 is ambiguous (the +run or build itself may be missing), so `GetAsync` and `GetBytesAsync` throw an `ApifyApiException` there instead. `LogOptions` fields: `Raw` (`bool?`, return the unprocessed log rather than the parsed form) and `Download` (`bool?`, request a download `Content-Disposition`). diff --git a/docs/models.md b/docs/models.md index 6cec4ac..00b8d42 100644 --- a/docs/models.md +++ b/docs/models.md @@ -41,7 +41,7 @@ Base class for every JSON-backed model below. | `IsPublic` | `bool?` | Whether the Actor is published publicly in the Apify Store. | | `CreatedAt` | `string?` | ISO 8601 creation timestamp. | | `ModifiedAt` | `string?` | ISO 8601 last-modification timestamp. | -| `StandbyUrl` | `string?` | URL of the Actor's server when it is a Server Actor (see [`ActorServerClient`](actors.md#server-actors--clientactoridserver)); `null` otherwise. | +| `StandbyUrl` | `string?` | URL of the Actor's server when it is a Server Actor (see [`ActorServerClient`](actors.md#server-actors-clientactoridserver)); `null` otherwise. | | `ActorStandby` | `JsonObject?` | The Server Actor settings: `isEnabled`, `build`, `memoryMbytes`, `idleTimeoutSecs`, `tenancy` (`SINGLE_TENANT` or `MULTI_TENANT`) and others. | | `TaggedBuilds` | `JsonObject?` | The builds the Actor's tags point to, keyed by tag (e.g. `latest`); each value has `buildId` and `buildNumber`. | @@ -363,4 +363,4 @@ One page of a paginated listing, returned by every `ListAsync` method. | `Desc` | `bool` | Whether the listing is in descending order. | To iterate every item across pages without managing offsets yourself, use the matching -`IterateAsync` method (an `IAsyncEnumerable`) instead — see the resource-specific docs. +`IterateAsync` method (an `IAsyncEnumerable`) instead; see the resource-specific docs. diff --git a/docs/runs.md b/docs/runs.md index 41f6621..5caa50d 100644 --- a/docs/runs.md +++ b/docs/runs.md @@ -11,13 +11,13 @@ Access the account-wide run collection with `client.Runs()`, an Actor's or task' `ListOptions` fields: `Offset`, `Limit`, `Desc` (standard pagination; `Limit` `0` or unset means no cap) and `ChunkSize` (the page size `IterateAsync` requests; the smaller of it and the remaining `Limit` is used per call). `RunListOptions` fields: `Status` (`IReadOnlyList?`, filter by one or more run statuses such as `SUCCEEDED`/`RUNNING`), -`Deployment` (`IReadOnlyList?`, filters Actor Standby runs by the pool serving them: `SINGLE_TENANT`, +`Deployment` (`IReadOnlyList?`, filters Server Actor runs by the pool that serves them: `SINGLE_TENANT`, `MULTI_TENANT_FREE_USERS`, `MULTI_TENANT_PAYING_USERS` or `MULTI_TENANT_OWNER_PAID`; requires `Status` with only -non-terminal statuses and is honoured only by the account-wide and Actor-scoped run collections), -`StartedAfter` and `StartedBefore` (ISO 8601 bounds, honoured only by the account-wide and Actor-scoped run +non-terminal statuses and is honored only by the account-wide and Actor-scoped run collections), +`StartedAfter` and `StartedBefore` (ISO 8601 bounds, honored only by the account-wide and Actor-scoped run collections, not by task runs). -## Single run — `client.Run(runId)` +## Single run: `client.Run(runId)` - `GetAsync(int? waitForFinishSecs = null)` → `ActorRun?`. - `UpdateAsync(object newFields)` → `ActorRun`. @@ -25,21 +25,21 @@ collections, not by task runs). - `AbortAsync(bool? gracefully = null)` → `ActorRun`. - `MetamorphAsync(string targetActorId, object? input = null, MetamorphOptions? options = null)` → `ActorRun`. - `RebootAsync()` → `ActorRun`. -- `ResurrectAsync(RunResurrectOptions? options = null)` → `ActorRun` — resurrects a finished run (status `SUCCEEDED`, `FAILED`, `ABORTED` or `TIMED-OUT`) and restarts it with the same storages. -- `ChargeAsync(RunChargeOptions options)` → `Task` — record pay-per-event charges (idempotent). -- `WaitForFinishAsync(int? waitSecs = null)` → `ActorRun` — client-side polling until terminal (`null` +- `ResurrectAsync(RunResurrectOptions? options = null)` → `ActorRun`: resurrects a finished run (status `SUCCEEDED`, `FAILED`, `ABORTED`, or `TIMED-OUT`) and restarts it with the same storages. +- `ChargeAsync(RunChargeOptions options)` → `Task`: record pay-per-event charges (idempotent). +- `WaitForFinishAsync(int? waitSecs = null)` → `ActorRun`: client-side polling until terminal (`null` waits indefinitely). A run id that keeps answering 404 for 3 seconds straight (replica lag resolves faster than that) throws `NotFoundException` instead of polling for the rest of `waitSecs`. -- `IterateDatasetItemsAsync(RunIterateDatasetItemsOptions? options = null)` → `IAsyncEnumerable` — +- `IterateDatasetItemsAsync(RunIterateDatasetItemsOptions? options = null)` → `IAsyncEnumerable`: lazily iterates the run's default dataset while the run is still producing items: it reads the rows below the dataset's `itemCount`, waits for the run to finish between polls, and after the run finishes reads any remaining rows. On a `LastRun()` client it sticks to the run its first request resolved to. -- `Dataset()`, `KeyValueStore()`, `RequestQueue()` — the run's default storages. Their own +- `Dataset()`, `KeyValueStore()`, `RequestQueue()`: the run's default storages. Their own `GetAsync()`/`DeleteAsync()` throw on a 404 instead of resolving `null`/no-op, since the run itself (not just the storage) could be what's missing; a record/request looked up by its own key or id (e.g. `GetRecordAsync`, `GetRequestAsync`) is unaffected and still resolves `null` for a missing one. -- `Log()` → `LogClient` (its `GetAsync`/`StreamAsync` methods are documented under [Logs in misc.md](misc.md#logs--clientlogbuildorrunid); on this run-nested client they throw rather than resolve `null`, for the same reason); `GetStreamedLogAsync()` → `Stream` (live raw log). -- `GetStreamedLog(Action toLog, bool fromStart = true)` → `StreamedLog` — redirects the run's live +- `Log()` → `LogClient` (its `GetAsync`/`StreamAsync` methods are documented under [Logs in misc.md](misc.md#logs-clientlogbuildorrunid); on this run-nested client they throw rather than resolve `null`, for the same reason); `GetStreamedLogAsync()` → `Stream` (live raw log). +- `GetStreamedLog(Action 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. diff --git a/docs/schedules.md b/docs/schedules.md index 01ec000..2d3d980 100644 --- a/docs/schedules.md +++ b/docs/schedules.md @@ -9,7 +9,7 @@ Schedules automatically start Actor or task runs at specified times. Access the `IterateAsync(ListOptions? options = null)` → `IAsyncEnumerable` (lazy, all pages). - `CreateAsync(object schedule)` → `Schedule`. -## Single schedule — `client.Schedule(id)` +## Single schedule: `client.Schedule(id)` - `GetAsync()` → `Schedule?`; `UpdateAsync(object newFields)` → `Schedule`; `DeleteAsync()`. - `GetLogAsync()` → `string` (invocation log; throws if the schedule does not exist). diff --git a/docs/storages.md b/docs/storages.md index f81802f..87a869f 100644 --- a/docs/storages.md +++ b/docs/storages.md @@ -1,14 +1,13 @@ # Storages -The three storage types — datasets, key-value stores and request queues — share the same collection +The three storage types (datasets, key-value stores, and request queues) share the same collection shape: `ListAsync(StorageListOptions? options = null)` (one page), `IterateAsync(StorageListOptions? options = null)` → `IAsyncEnumerable` (lazy, all pages), and `GetOrCreateAsync(string? name = null)` (dataset and key-value store collections additionally accept an optional `JsonNode? schema = null` to register a storage schema on creation). Storages can also be reached from a run (`client.Run(id).Dataset()`, `.KeyValueStore()`, `.RequestQueue()`). -> Snippets below run inside an `async` context. `ImplicitUsings` is disabled in this repository, so all -> `using` directives (including `System`) are shown explicitly and must precede any statements. +> Snippets run in an `async` context with `ImplicitUsings` disabled, so every `using`, `System` included, is listed. `StorageListOptions` fields: @@ -30,7 +29,7 @@ reached from a run (`client.Run(id).Dataset()`, `.KeyValueStore()`, `.RequestQue response headers). The page's `Count` is the larger of the dataset rows the API scanned and the items returned, so `Clean`/`SkipEmpty` can leave it above `Items.Count`; to page by hand, request the next page at `Offset + Math.Min(Count, Limit)`, and use `Items.Count` for the number of items. -- `IterateItemsAsync(DatasetListItemsOptions? options = null)` → `IAsyncEnumerable` — lazily iterate every +- `IterateItemsAsync(DatasetListItemsOptions? options = null)` → `IAsyncEnumerable`: lazily iterate every item across pages, fetching each page on demand. - `DownloadItemsAsync(DownloadItemsFormat format, DatasetDownloadOptions? options = null)` → serialized items as `byte[]` (raw bytes, so binary formats like `Xlsx` are not corrupted; decode text formats yourself). @@ -38,7 +37,7 @@ reached from a run (`client.Run(id).Dataset()`, `.KeyValueStore()`, `.RequestQue `Stream`, so a large export is processed incrementally instead of buffered into memory. Unlike `DownloadItemsAsync`, this is a single attempt (not retried); dispose the stream when done, which also releases the underlying connection. -- `PushItemsAsync(object items)` — push one object or an array of objects (or a string holding their JSON +- `PushItemsAsync(object items)`: push one object or an array of objects (or a string holding their JSON serialization). Anything else, such as a bare number or an array of non-objects, throws `ArgumentException` before a request is sent, because the API accepts only objects. - `GetStatisticsAsync()` → `JsonObject` (throws if the dataset does not exist). @@ -106,12 +105,12 @@ await csvStream.CopyToAsync(file); - `GetAsync()` → `KeyValueStore?`; `UpdateAsync(object newFields)` → `KeyValueStore`; `DeleteAsync()`. - `ListKeysAsync(ListKeysOptions? options = null)` → `KeyValueStoreKeysPage` (one page). -- `IterateKeysAsync(IterateKeysOptions? options = null)` → `IAsyncEnumerable` — lazily +- `IterateKeysAsync(IterateKeysOptions? options = null)` → `IAsyncEnumerable`: lazily iterate every key across pages, fetching each page (sized by `ChunkSize`) on demand. - `RecordExistsAsync(string key)` → `bool`. - `GetRecordAsync(string key, GetRecordOptions? options = null)` → `KeyValueStoreRecord?`. `KeyValueStoreRecord.Value` is a `byte[]` of the record's raw bytes (so binary records survive intact); - decode it according to `KeyValueStoreRecord.ContentType` — e.g. `Encoding.UTF8.GetString(record.Value)` + decode it according to `KeyValueStoreRecord.ContentType`, for example `Encoding.UTF8.GetString(record.Value)` for text, or `JsonSerializer.Deserialize(record.Value)` for JSON. - `SetRecordAsync(string key, byte[] value, string contentType, SetRecordOptions? options = null)` and `SetRecordJsonAsync(string key, object? value)` (serializes `value` to JSON bytes). @@ -122,10 +121,10 @@ await csvStream.CopyToAsync(file); `ListKeysOptions` fields: `Limit` (page size), `ExclusiveStartKey` (start after this key), `Prefix` (only keys with this prefix), `Collection` (a named record collection), and `Signature` (for signed public URLs). `IterateKeysOptions` fields: `Limit` (total across all pages), -`ChunkSize` (page size; client-side only, never sent to the API — defaults to 1000; `MaxPageLimit` is its +`ChunkSize` (page size, defaults to 1000; client-side only, never sent to the API; `MaxPageLimit` is its older name, and `ChunkSize` wins if both are set), `ExclusiveStartKey`, `Collection`, and `Prefix`. `GetRecordOptions` fields: `Attachment` (request a download disposition) and `Signature`. `SetRecordOptions` fields: `TimeoutSecs`, `DoNotRetryTimeouts`, -and `ContentEncoding` (upload a value that is already compressed — see below). +and `ContentEncoding` (upload a value that is already compressed; see below). ```csharp using System; @@ -154,7 +153,7 @@ await client.KeyValueStore(store.Id!).SetRecordAsync("blob", new byte[] { 0x00, ### Uploading a pre-compressed record `SetRecordOptions.ContentEncoding` uploads a value you already compressed yourself (`gzip`, `br`, -`deflate`, or `identity`), sent verbatim as the `Content-Encoding` header: the client sends the bytes +`zstd`, `deflate`, or `identity`), sent verbatim as the `Content-Encoding` header: the client sends the bytes as-is and skips its own compression, so they are never compressed twice. ```csharp @@ -200,16 +199,16 @@ options set a stable `ClientKey` (required to manage locks the client created) a `ListAndLockHeadAsync(int lockSecs, int? limit = null)` → `LockedRequestQueueHead` (each item's `RequestQueueRequest.LockExpiresAt`/`RetryCount` is populated). - `BatchAddRequestsAsync(IReadOnlyList requests, bool forefront = false, BatchAddRequestsOptions? options = null)` - → `BatchAddResult` — auto-chunks by count (25) and payload size (~9 MiB) and retries unprocessed + → `BatchAddResult`: auto-chunks by count (25) and payload size (~9 MiB) and retries unprocessed requests. Every request needs a non-empty `UniqueKey`. -- `BatchDeleteRequestsAsync(IReadOnlyList requests)` → `BatchDeleteResult` — delete a +- `BatchDeleteRequestsAsync(IReadOnlyList requests)` → `BatchDeleteResult`: delete a batch of requests in one call; each entry identifies the request to delete via `Id` and/or `UniqueKey`. - `ListRequestsAsync(ListRequestsOptions? options = null)` → `RequestQueueRequestsPage` and `PaginateRequestsAsync(PaginateRequestsOptions? options = null)` → `IAsyncEnumerable`. - Lock management: `ProlongRequestLockAsync(string id, int lockSecs, bool forefront = false)` → `RequestLockInfo`, `DeleteRequestLockAsync(string id, bool forefront = false)`, `UnlockRequestsAsync()` → `UnlockRequestsResult`. -- `WithClientKey(string clientKey)` → `RequestQueueClient` — returns a copy of this client bound to the +- `WithClientKey(string clientKey)` → `RequestQueueClient`: returns a copy of this client bound to the given client key (a fluent alternative to passing `RequestQueueClientOptions.ClientKey` on `client.RequestQueue(id, options)`); the client key ties lock ownership to this client. @@ -217,7 +216,7 @@ options set a stable `ClientKey` (required to manage locks the client created) a leaves unprocessed), `MaxParallel` (how many chunks are sent concurrently), and `MinDelayBetweenUnprocessedRequestsRetriesMillis` (backoff before retrying unprocessed requests). `ListRequestsOptions`/`PaginateRequestsOptions` fields: `Limit`, `ExclusiveStartId`, `Cursor`, and -`Filter` (an `IReadOnlyList?` — one or more of `"pending"`/`"locked"`, so several states can be +`Filter` (an `IReadOnlyList?` of one or more of `"pending"`/`"locked"`, so several states can be requested at once); `PaginateRequestsOptions` also has `ChunkSize` (page size, defaults to 1000; `MaxPageLimit` is its older name). ```csharp diff --git a/docs/tasks.md b/docs/tasks.md index 76ec1a5..f41e54c 100644 --- a/docs/tasks.md +++ b/docs/tasks.md @@ -9,16 +9,16 @@ a specific task with `client.Task(id)`. `IterateAsync(ListOptions? options = null)` → `IAsyncEnumerable` (lazy, all pages). - `CreateAsync(object task)` → `ActorTask`. -## Single task — `client.Task(id)` +## Single task: `client.Task(id)` - `GetAsync()` → `ActorTask?`; `UpdateAsync(object newFields)` → `ActorTask`; `DeleteAsync()`. -- `PublishAsync()` → `ActorTask` — publishes the task on its public landing page in Apify Store +- `PublishAsync()` → `ActorTask`: publishes the task on its public landing page in Apify Store (sets `isPublic: true`). The task's Actor must be public, `PublicConfig.InputSchemaFields` and `PublicConfig.DatasetView` must already be set, and the Actor must not already have 10 published tasks (accounts are capped at 100 published tasks across all Actors). If any condition isn't met, the publish request fails and `isPublic` is left unchanged. Requires write permission to the task's Actor. Publishing an already published task does nothing. -- `UnpublishAsync()` → `ActorTask` — unpublishes the task (sets `isPublic: false`); `PublicConfig` is +- `UnpublishAsync()` → `ActorTask`: unpublishes the task (sets `isPublic: false`); `PublicConfig` is preserved so the task can be published again without re-entering it. Requires write permission to the task's Actor. Unpublishing a task that is not published does nothing. - `StartAsync(object? input = null, TaskStartOptions? options = null)` → `ActorRun`. diff --git a/docs/webhooks.md b/docs/webhooks.md index 6243eb8..8707ef8 100644 --- a/docs/webhooks.md +++ b/docs/webhooks.md @@ -4,7 +4,7 @@ Webhooks notify an external service when specific events occur. Access the accou `client.Webhooks()` and a specific webhook with `client.Webhook(id)`. Webhook dispatches are read with `client.WebhookDispatches()` and `client.WebhookDispatch(id)`. -## Webhook collection — `client.Webhooks()` +## Webhook collection: `client.Webhooks()` - `ListAsync(ListOptions? options = null)` → `PaginationList`; `IterateAsync(ListOptions? options = null)` → `IAsyncEnumerable` (lazy, all pages). Webhook @@ -15,7 +15,7 @@ Webhooks nested under an Actor or task (`client.Actor(id).Webhooks()`, `client.T are **read-only** (list only); create account-wide webhooks that target an Actor/task via the webhook's `condition`. -## Single webhook — `client.Webhook(id)` +## Single webhook: `client.Webhook(id)` - `GetAsync()`, `UpdateAsync(newFields)`, `DeleteAsync()`. - `TestAsync()` → `WebhookDispatch` (dispatch immediately). diff --git a/src/Apify.Client/Apify.Client.csproj b/src/Apify.Client/Apify.Client.csproj index 9d450ce..a2cc56c 100644 --- a/src/Apify.Client/Apify.Client.csproj +++ b/src/Apify.Client/Apify.Client.csproj @@ -6,7 +6,7 @@ Apify.Client - 0.9.0 + 0.10.0 Apify Apify Apify API client for .NET @@ -35,4 +35,8 @@ + + + + diff --git a/src/Apify.Client/ApifyClient.cs b/src/Apify.Client/ApifyClient.cs index 3f4bcde..e87cfca 100644 --- a/src/Apify.Client/ApifyClient.cs +++ b/src/Apify.Client/ApifyClient.cs @@ -12,24 +12,13 @@ namespace Apify.Client; /// -/// The entry point for interacting with the Apify API. +/// The entry point of the client: create it with an API token (and optionally ), then +/// get resource clients from its accessors, such as , , and . /// /// -/// -/// See the project's top-level README for the client's official-but-experimental status. -/// -/// -/// Construct it with an API token (and optional settings via ), then -/// obtain resource clients via the accessor methods, e.g. , , -/// . -/// -/// -/// Architecture. The public interface is this class and the resource clients it returns. The -/// replaceable transport is the (default ); -/// pass a custom one via . Cross-cutting behaviour (auth, -/// User-Agent, retries with exponential backoff, timeouts) lives in the internal HTTP client and is -/// applied to every request. -/// +/// Authentication, the User-Agent header, retries with exponential backoff, and timeouts are applied to every +/// request. Pass a custom in to replace +/// the default . /// public sealed class ApifyClient { @@ -51,10 +40,10 @@ public sealed class ApifyClient /// Default duration of the Medium timeout tier, in seconds. public const int DefaultTimeoutMediumSecs = 30; - /// Environment variable that signals the client is running on the Apify platform. + /// Set to a non-empty value when the client runs on the Apify platform. private const string EnvIsAtHome = "APIFY_IS_AT_HOME"; - /// Environment variable holding the current Actor run's id (set on the platform). + /// The ID of the current Actor run, set on the platform. private const string EnvActorRunId = "ACTOR_RUN_ID"; /// Addresses the current user (/users/me). @@ -97,8 +86,8 @@ public ApifyClient(ApifyClientOptions options) } /// - /// The compressor for large request bodies: the explicit , or the - /// built-in one for with its default quality. + /// The compressor for large request bodies: , or the built-in one for + /// . /// private static HttpCompressor ResolveCompressor(ApifyClientOptions options) { @@ -107,18 +96,21 @@ private static HttpCompressor ResolveCompressor(ApifyClientOptions options) return options.Compressor; } - return options.RequestCompression == RequestCompression.Gzip ? new GzipHttpCompressor() : new BrotliHttpCompressor(); + return options.RequestCompression switch + { + RequestCompression.Gzip => new GzipHttpCompressor(), + RequestCompression.Zstd => new ZstdHttpCompressor(), + _ => new BrotliHttpCompressor(), + }; } - /// The /v2 API version path, appended to a base URL that does not already end with it. + /// The API version path, appended to a base URL that does not end with it. private const string ApiVersionPath = "/v2"; /// - /// Normalizes a caller-supplied base URL into one ending in the API version path: trailing slashes are - /// stripped, and is appended unless the URL already ends with it (so passing - /// https://api.apify.com/v2 does not double up into .../v2/v2). Only an exact /v2 - /// suffix counts; another version segment (e.g. /v3) is treated as part of the host path and still - /// gets /v2 appended, since the client's endpoints only exist under v2. + /// Normalizes a base URL to end with : trailing slashes are stripped and the path is + /// appended unless the URL already ends with /v2. Another version segment such as /v3 is kept as part of + /// the host path, since the endpoints only exist under v2. /// private static string ToApiBaseUrl(string url) { @@ -127,22 +119,17 @@ private static string ToApiBaseUrl(string url) } /// - /// The User-Agent header value this client sends: the built-in one, or the User-Agent entry of - /// (matched case-insensitively) when it overrides it. + /// The User-Agent this client sends: the built-in one, or the User-Agent entry of + /// when it overrides it. /// public string UserAgent => _http.UserAgent; - /// - /// Counters of the calls and requests this client has made and of the rate limit errors it has met, as in - /// the reference client's stats. - /// + /// Counters of the calls and requests this client made and the rate limit errors it met. public Statistics Stats => _stats; - /// The fully-qualified API base URL this client targets (including the /v2 suffix). + /// The API base URL this client targets, including the /v2 suffix. public string ApiBaseUrl => _baseUrl; - // ----- Actor accessors ----------------------------------------------------- - /// A client for the Actor collection (list & create Actors). public ActorCollectionClient Actors() => new(_http, _baseUrl); @@ -150,8 +137,6 @@ private static string ToApiBaseUrl(string url) /// The Actor ID or username~name. public ActorClient Actor(string id) => new(this, _http, _baseUrl, RequireNonEmptyId(id, nameof(id))); - // ----- Build accessors ----------------------------------------------------- - /// A client for the Actor build collection (list builds). public BuildCollectionClient Builds() => new(_http, _baseUrl, "actor-builds"); @@ -159,8 +144,6 @@ private static string ToApiBaseUrl(string url) /// The build ID. public BuildClient Build(string id) => new(_http, _baseUrl, RequireNonEmptyId(id, nameof(id))); - // ----- Run accessors ------------------------------------------------------- - /// A client for the Actor run collection (list runs). public RunCollectionClient Runs() => new(_http, _baseUrl, "actor-runs"); @@ -168,8 +151,6 @@ private static string ToApiBaseUrl(string url) /// The run ID. public RunClient Run(string id) => new(this, _http, _baseUrl, "actor-runs", RequireNonEmptyId(id, nameof(id))); - // ----- Dataset accessors --------------------------------------------------- - /// A client for the dataset collection (list & get-or-create datasets). public DatasetCollectionClient Datasets() => new(_http, _baseUrl); @@ -177,8 +158,6 @@ private static string ToApiBaseUrl(string url) /// The dataset ID or name. public DatasetClient Dataset(string id) => DatasetClient.ForId(_http, _baseUrl, RequireNonEmptyId(id, nameof(id))).WithPublicBase(_publicBaseUrl); - // ----- Key-value store accessors ------------------------------------------- - /// A client for the key-value store collection. public KeyValueStoreCollectionClient KeyValueStores() => new(_http, _baseUrl); @@ -186,23 +165,18 @@ private static string ToApiBaseUrl(string url) /// The store ID or name. public KeyValueStoreClient KeyValueStore(string id) => KeyValueStoreClient.ForId(_http, _baseUrl, RequireNonEmptyId(id, nameof(id))).WithPublicBase(_publicBaseUrl); - // ----- Request queue accessors --------------------------------------------- - /// A client for the request queue collection. public RequestQueueCollectionClient RequestQueues() => new(_http, _baseUrl); /// - /// A client for a specific request queue, addressed by ID or name. Must not be empty. Optionally pass - /// options to set a stable ClientKey and/or a per-request TimeoutSecs for this queue's - /// calls. + /// A client for a specific request queue, addressed by ID or name. Must not be empty. The options set a stable + /// ClientKey and a per-request TimeoutSecs for this queue's calls. /// /// The queue ID or name. /// Optional per-queue-client options. public RequestQueueClient RequestQueue(string id, Options.RequestQueueClientOptions? options = null) => RequestQueueClient.ForId(_http, _baseUrl, RequireNonEmptyId(id, nameof(id)), options); - // ----- Task accessors ------------------------------------------------------ - /// A client for the Actor task collection (list & create tasks). public TaskCollectionClient Tasks() => new(_http, _baseUrl); @@ -210,8 +184,6 @@ public RequestQueueClient RequestQueue(string id, Options.RequestQueueClientOpti /// The task ID. public TaskClient Task(string id) => new(this, _http, _baseUrl, RequireNonEmptyId(id, nameof(id))); - // ----- Schedule accessors -------------------------------------------------- - /// A client for the schedule collection (list & create schedules). public ScheduleCollectionClient Schedules() => new(_http, _baseUrl); @@ -219,8 +191,6 @@ public RequestQueueClient RequestQueue(string id, Options.RequestQueueClientOpti /// The schedule ID. public ScheduleClient Schedule(string id) => new(_http, _baseUrl, RequireNonEmptyId(id, nameof(id))); - // ----- Webhook accessors --------------------------------------------------- - /// A client for the webhook collection (list & create webhooks). public WebhookCollectionClient Webhooks() => new(_http, _baseUrl); @@ -235,8 +205,6 @@ public RequestQueueClient RequestQueue(string id, Options.RequestQueueClientOpti /// The dispatch ID. public WebhookDispatchClient WebhookDispatch(string id) => new(_http, _baseUrl, RequireNonEmptyId(id, nameof(id))); - // ----- Misc accessors ------------------------------------------------------ - /// A client for browsing the Apify Store. public StoreCollectionClient Store() => new(_http, _baseUrl); @@ -252,17 +220,13 @@ public RequestQueueClient RequestQueue(string id, Options.RequestQueueClientOpti public UserClient User(string id) => new(_http, _baseUrl, RequireNonEmptyId(id, nameof(id))); /// - /// Sets the status message of the current Actor run. + /// Sets the status message of the current Actor run, the one in the ACTOR_RUN_ID environment variable. A + /// terminal message is final and is not overwritten. /// - /// - /// This convenience method updates the run identified by the ACTOR_RUN_ID environment variable, - /// so it only works when called from inside an Actor run. If is true, the - /// message becomes final and won't be overwritten. Throws if - /// ACTOR_RUN_ID is not set. - /// /// The status message to set. /// Whether the message is final. /// A token to cancel the request. + /// ACTOR_RUN_ID is not set. public Task SetStatusMessageAsync(string message, bool isTerminal = false, CancellationToken cancellationToken = default) { var runId = Environment.GetEnvironmentVariable(EnvActorRunId); @@ -282,11 +246,8 @@ public Task SetStatusMessageAsync(string message, bool isTerminal = fa private static string TrimTrailingSlash(string value) => value.TrimEnd('/'); /// - /// Validates that an id/name passed to a resource accessor is non-empty, returning it unchanged so it - /// can be used inline at the call site. An empty id would otherwise flow through - /// into a collection-shaped URL (e.g. an empty run id - /// addressing .../actor-runs/) and silently mis-address the wrong resource instead of failing - /// loudly, matching the reference client's uniform id.min(1) validation on every accessor. + /// Rejects an empty ID, which would otherwise address a collection URL such as .../actor-runs/. Returns the + /// ID so it can be used inline. /// private static string RequireNonEmptyId(string id, string paramName) { @@ -298,10 +259,7 @@ private static string RequireNonEmptyId(string id, string paramName) return id; } - /// - /// Reports whether the client is running on the Apify platform, by reading the APIFY_IS_AT_HOME - /// environment variable (set to a non-empty value on the platform). - /// + /// Whether APIFY_IS_AT_HOME is set to a non-empty value. private static bool DefaultIsAtHome() { var value = Environment.GetEnvironmentVariable(EnvIsAtHome); @@ -309,8 +267,7 @@ private static bool DefaultIsAtHome() } /// - /// Builds the User-Agent header value mandated by the client requirements: - /// ApifyClient/{version} ({os}; .NET/{runtimeVersion}); isAtHome/{true|false}. + /// Builds the User-Agent value ApifyClient/{version} ({os}; .NET/{runtimeVersion}); isAtHome/{true|false}. /// private static string BuildUserAgent(string? suffix, Func isAtHomeFn) { @@ -332,15 +289,12 @@ private static string BuildUserAgent(string? suffix, Func isAtHomeFn) } /// - /// Additional Node os.platform() tokens for Unix platforms that .NET has no dedicated - /// helper for, matched via . - /// Solaris and illumos both report as sunos, matching Node. + /// OS tokens of Unix platforms that has no helper for. Solaris and illumos both + /// report sunos, as in Node. /// /// - /// These entries are deliberately kept even though .NET has no officially supported runtime on these - /// platforms today: the requirement is that every Apify client emit the exact same OS token as the - /// reference client's os.platform(), so should .NET ever run there the token stays aligned - /// rather than degrading to unknown. They are a forward-compatible superset, not live branches. + /// .NET has no supported runtime on these platforms today. They keep the token the same as the reference client's + /// os.platform() should one appear. /// private static readonly (OSPlatform Platform, string Token)[] ExtendedOsTokens = { @@ -351,7 +305,7 @@ private static readonly (OSPlatform Platform, string Token)[] ExtendedOsTokens = (OSPlatform.Create("AIX"), "aix"), }; - /// Resolves the current platform to its User-Agent OS token (see ). + /// The User-Agent OS token of the current platform. private static string CurrentOs() => ResolveOsToken( OperatingSystem.IsWindows(), OperatingSystem.IsMacOS(), @@ -361,15 +315,10 @@ private static string CurrentOs() => ResolveOsToken( RuntimeInformation.IsOSPlatform); /// - /// Maps the detected runtime platform to the short, lowercase token used in the User-Agent OS - /// field. The tokens are exactly the reference JS client's Node os.platform() values - /// (win32, darwin, android, linux, freebsd, openbsd, - /// netbsd, sunos, aix), so the token is identical across Apify clients. Android is - /// checked before Linux because an Android runtime is also Linux-based. The five platforms .NET exposes - /// dedicated helpers for are matched first; the remaining Unix platforms - /// Node names but .NET has no helper for are matched via . Platforms the - /// reference's Node runtime cannot run on at all (e.g. iOS, the browser) have no reference token and so - /// fall back to unknown. + /// Maps the platform to the OS token of the User-Agent: the values of Node's os.platform() (win32, + /// darwin, android, linux, freebsd, openbsd, netbsd, sunos, + /// aix), so the token is the same in every Apify client. Android is checked before Linux, which it is based on. + /// A platform Node does not run on has no token and maps to unknown. /// internal static string ResolveOsToken( bool isWindows, diff --git a/src/Apify.Client/ApifyClientOptions.cs b/src/Apify.Client/ApifyClientOptions.cs index 5fd87c0..fc4cd65 100644 --- a/src/Apify.Client/ApifyClientOptions.cs +++ b/src/Apify.Client/ApifyClientOptions.cs @@ -4,27 +4,22 @@ namespace Apify.Client; -/// -/// Configuration for an . All fields have sensible defaults; set only the ones -/// you need to override. -/// +/// Configuration of an . Set only the fields you need to override. public sealed class ApifyClientOptions { /// API token, sent as a Bearer token. public string? Token { get; set; } /// - /// API base URL, with or without the /v2 API version path: the path is appended when not already - /// present, so https://api.apify.com and https://api.apify.com/v2 are both accepted and - /// resolve to the same . + /// API base URL, with or without the /v2 path (it is appended when missing), so https://api.apify.com and + /// https://api.apify.com/v2 give the same . /// public string BaseUrl { get; set; } = ApifyClient.DefaultBaseUrl; /// - /// Base URL for building public, shareable resource URLs (defaults to , - /// whatever is, as in the reference client, so a client behind an internal or proxied - /// does not hand out internal links). Like , accepted with or - /// without the /v2 API version path. + /// Base URL of public, shareable resource URLs, with or without /v2. Defaults to + /// whatever is, as in the reference client, so a client + /// behind an internal does not hand out internal links. /// public string? PublicBaseUrl { get; set; } @@ -38,30 +33,22 @@ public sealed class ApifyClientOptions public int? MaxDelayBetweenRetriesMillis { get; set; } /// - /// The overall per-request cap, in seconds (default 360), matching the reference client's - /// timeoutMaxSecs: every attempt's growing timeout, including one set with WithTimeout, is - /// clamped to it, regardless of tier. It is also the base of the Long tier unless - /// is set. + /// The overall per-request cap in seconds (default 360), as the reference client's timeoutMaxSecs: every + /// attempt's timeout, including one set with WithTimeout, is clamped to it. It is also the base of the + /// Long tier unless is set. /// public int TimeoutSecs { get; set; } = ApifyClient.DefaultTimeoutSecs; /// - /// Base duration of the Long timeout tier, in seconds: downloads, uploads and streaming. Defaults to - /// . Matches the reference client's timeoutLongSecs; the cap - /// still applies to it. + /// Base duration of the Long timeout tier in seconds: downloads, uploads, and streaming. Defaults to + /// , which still caps it. /// public int? TimeoutLongSecs { get; set; } - /// - /// Base duration of the Short timeout tier, in seconds (default 5): simple metadata reads and - /// writes (get/update/delete a resource). Matches the reference client's timeoutShortSecs. - /// + /// Base duration of the Short timeout tier in seconds (default 5): metadata reads and writes. public int TimeoutShortSecs { get; set; } = ApifyClient.DefaultTimeoutShortSecs; - /// - /// Base duration of the Medium timeout tier, in seconds (default 30): listing, batch and trigger - /// calls. Matches the reference client's timeoutMediumSecs. - /// + /// Base duration of the Medium timeout tier in seconds (default 30): listing, batch, and trigger calls. public int TimeoutMediumSecs { get; set; } = ApifyClient.DefaultTimeoutMediumSecs; /// Custom suffix appended to the User-Agent header. @@ -69,25 +56,23 @@ public sealed class ApifyClientOptions /// /// Algorithm used to compress large request bodies (default ) with - /// its default quality. Set it to to send gzip-compressed bodies - /// instead. Use to tune the quality or plug in another algorithm. + /// its default quality. Use to tune the quality or plug in another algorithm. /// public RequestCompression RequestCompression { get; set; } = RequestCompression.Brotli; /// - /// A custom request body compressor, for a non-default quality (, - /// ) or another algorithm (derive from ). - /// When set, it takes precedence over . Bodies are compressed only when - /// they are large enough to benefit and their content type does not already carry its own compression. + /// A custom request body compressor, for another quality (, + /// , ) or another algorithm (derive from + /// ). It takes precedence over . Only large bodies without + /// their own compression are compressed. /// public HttpCompressor? Compressor { get; set; } /// - /// Additional headers sent with every request, such as an integration platform marker. They win over the - /// client's built-in defaults (User-Agent, Authorization); header names are compared - /// case-insensitively. Only request headers are allowed: a content header such as Content-Type - /// makes the constructor throw , because the client - /// sets those per request. + /// Additional headers sent with every request, such as an integration marker. They win over the built-in + /// User-Agent and Authorization; names are case-insensitive. A content header such as Content-Type + /// makes the constructor throw , because the client sets + /// those per request. /// public IReadOnlyDictionary? Headers { get; set; } diff --git a/src/Apify.Client/ApifyClientVersion.cs b/src/Apify.Client/ApifyClientVersion.cs index a56fe8b..65cd9a0 100644 --- a/src/Apify.Client/ApifyClientVersion.cs +++ b/src/Apify.Client/ApifyClientVersion.cs @@ -14,11 +14,11 @@ public static class ApifyClientVersion /// 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 string ClientVersion = "0.9.0"; + public const string ClientVersion = "0.10.0"; /// /// The version of the Apify OpenAPI specification this client was generated and verified against. /// Corresponds to the info.version field of the Apify OpenAPI document. /// - public const string ApiSpecVersion = "v2-2026-11-04T151257Z"; + public const string ApiSpecVersion = "v2-2026-11-09T143926Z"; } diff --git a/src/Apify.Client/Exceptions/ActorServerException.cs b/src/Apify.Client/Exceptions/ActorServerException.cs index 4894cb4..df462b8 100644 --- a/src/Apify.Client/Exceptions/ActorServerException.cs +++ b/src/Apify.Client/Exceptions/ActorServerException.cs @@ -1,10 +1,9 @@ namespace Apify.Client.Exceptions; /// -/// Thrown by when the Actor cannot be used as a Server Actor: it does -/// not serve a web server, or the build that should serve its server interface does not exist. A missing Actor -/// is reported as the usual instead. Matches the reference client's -/// ApifyClientError for these cases. +/// Thrown by when the Actor cannot be used as a Server Actor: it has no web +/// server, or the build that should serve its server interface does not exist. A missing Actor throws an +/// instead. /// public sealed class ActorServerException : ApifyClientException { diff --git a/src/Apify.Client/Exceptions/ApifyApiException.cs b/src/Apify.Client/Exceptions/ApifyApiException.cs index 5a0628d..403038b 100644 --- a/src/Apify.Client/Exceptions/ApifyApiException.cs +++ b/src/Apify.Client/Exceptions/ApifyApiException.cs @@ -5,13 +5,11 @@ namespace Apify.Client.Exceptions; -/// -/// Thrown for HTTP requests that reach the Apify API but receive a non-success status code. -/// +/// Thrown when the Apify API answers with a non-success status. /// -/// It mirrors the ApifyApiError of the reference JavaScript client and exposes the parsed error -/// , the human-readable , the HTTP , -/// the number of the final , and the request /. +/// Mirrors the reference client's ApifyApiError: the error , , +/// , the final and the request and +/// . /// public class ApifyApiException : ApifyClientException { @@ -48,7 +46,7 @@ public ApifyApiException( /// The machine-readable error type returned by the API (e.g. record-not-found). public string? Type { get; } - /// The raw error message returned by the API, without the status/type prefix. + /// The error message returned by the API, without the status and type prefix. public string ApiMessage { get; } /// The number of the API call attempt that produced this error (1-based). @@ -60,10 +58,7 @@ public ApifyApiException( /// The path of the API endpoint (URL excluding origin). public string Path { get; } - /// - /// Additional structured data provided by the API about the error, if any. Named ErrorData - /// (not Data) to avoid hiding . - /// + /// Additional structured error data from the API. Not named Data, to avoid hiding . public JsonObject? ErrorData { get; } private static string FormatMessage(int statusCode, string? type, string message) @@ -99,12 +94,9 @@ private static string FormatMessage(int statusCode, string? type, string message private const int StatusServerErrorFloor = 500; /// - /// Creates the subclass matching (see the - /// per-status types in this namespace, e.g. ), so callers can branch with - /// a type check instead of comparing status codes or error-type strings. Any other status falls back to - /// the base ; every subclass still satisfies an - /// is ApifyApiException/catch (ApifyApiException) check, matching the reference client's - /// ApifyApiError subclass hierarchy. + /// Creates the subclass for , such as + /// , so callers can branch on the type. Other statuses give the base type, as in the + /// reference client's ApifyApiError hierarchy. /// internal static ApifyApiException Create( int statusCode, diff --git a/src/Apify.Client/Exceptions/ApifyApiExceptionTypes.cs b/src/Apify.Client/Exceptions/ApifyApiExceptionTypes.cs index 260ed84..3711896 100644 --- a/src/Apify.Client/Exceptions/ApifyApiExceptionTypes.cs +++ b/src/Apify.Client/Exceptions/ApifyApiExceptionTypes.cs @@ -2,11 +2,9 @@ namespace Apify.Client.Exceptions; -// Each subclass below matches one HTTP status group and carries no behavior of its own: construction and -// message formatting stay in the base ApifyApiException. They let a caller branch on the error kind with a -// type check (`catch (NotFoundException)`) instead of comparing StatusCode/Type, matching the reference -// client's and the Python client's ApifyApiError subclass hierarchy. See ApifyApiException.Create, which -// picks the subclass for a given status code. +// Each subclass matches one HTTP status group and adds no behavior; construction and message formatting stay in +// ApifyApiException. Callers can catch the error kind (`catch (NotFoundException)`) instead of comparing the status. +// ApifyApiException.Create picks the subclass. /// Thrown for a 400 Bad Request response: the request was malformed or failed validation. public sealed class InvalidRequestException : ApifyApiException @@ -39,12 +37,9 @@ public ForbiddenException(int statusCode, string? type, string message, int atte } /// -/// Thrown for a 404 Not Found response that could not be resolved to a single, unambiguous missing -/// resource (see remarks on the client's resources). Most resource clients -/// addressed by id resolve an ordinary 404 to null instead of throwing; this type still surfaces for -/// chained clients that have no id of their own (e.g. a run's default dataset/key-value -/// store/request queue/log, or a build's log) and for the sub-path endpoints that always require their -/// parent resource to exist. +/// Thrown for a 404 Not Found response that is not resolved to a missing resource. Clients addressed by ID +/// resolve an ordinary 404 to null. This type surfaces for clients without their own ID (a run's default +/// storages and log, a build's log) and for sub-path endpoints whose parent resource must exist. /// public sealed class NotFoundException : ApifyApiException { @@ -66,9 +61,8 @@ public ConflictException(int statusCode, string? type, string message, int attem } /// -/// Thrown for a 429 Too Many Requests response that exhausted the client's retries. The client -/// already retries a rate-limited request with backoff (see ); this -/// type reaches the caller only once that budget is spent. +/// Thrown for a 429 Too Many Requests response once the client's retries (see +/// ) are spent. /// public sealed class RateLimitException : ApifyApiException { diff --git a/src/Apify.Client/Exceptions/ApifyClientException.cs b/src/Apify.Client/Exceptions/ApifyClientException.cs index 9a65b6a..24aec44 100644 --- a/src/Apify.Client/Exceptions/ApifyClientException.cs +++ b/src/Apify.Client/Exceptions/ApifyClientException.cs @@ -3,15 +3,13 @@ namespace Apify.Client.Exceptions; /// -/// Base class of every error this client throws for a failed API call: -/// (and its per-status subclasses), , -/// and . A single -/// catch (ApifyClientException) catches all of them, matching the reference client's -/// ApifyClientError. +/// Base class of the errors this client throws for a failed API call: and its +/// subclasses, , and +/// . Mirrors the reference client's ApifyClientError. /// /// -/// An invalid argument throws a standard / -/// before any request is sent; neither of those extends this class. +/// An invalid argument throws or before any +/// request is sent; these do not derive from this class. /// public abstract class ApifyClientException : Exception { diff --git a/src/Apify.Client/Exceptions/ApifyTransportException.cs b/src/Apify.Client/Exceptions/ApifyTransportException.cs index c2ab392..303d5cd 100644 --- a/src/Apify.Client/Exceptions/ApifyTransportException.cs +++ b/src/Apify.Client/Exceptions/ApifyTransportException.cs @@ -2,12 +2,10 @@ namespace Apify.Client.Exceptions; -/// -/// Marks a transport-level (network/timeout) failure, which is retryable by the client. -/// +/// A network or timeout failure, which the client retries. /// -/// A custom should throw this for connection, DNS and -/// timeout failures; a non-2xx HTTP status must be returned as a normal response instead. +/// A custom throws this for connection, DNS, and timeout failures, and +/// returns a non-2xx status as a response. /// public sealed class ApifyTransportException : ApifyClientException { diff --git a/src/Apify.Client/Exceptions/InvalidResponseBodyException.cs b/src/Apify.Client/Exceptions/InvalidResponseBodyException.cs index 712455d..647b51e 100644 --- a/src/Apify.Client/Exceptions/InvalidResponseBodyException.cs +++ b/src/Apify.Client/Exceptions/InvalidResponseBodyException.cs @@ -3,10 +3,9 @@ namespace Apify.Client.Exceptions; /// -/// Thrown when the body of a successful response does not parse as JSON, for example a proxy's HTML page -/// served with a success status. The response arrived in full, so the request is not retried. An error -/// status with an unparsable body throws the for its status code instead. -/// Matches the reference client's InvalidResponseBodyError. +/// Thrown when the body of a successful response is not JSON, for example a proxy's HTML page. The response arrived in +/// full, so it is not retried. An error status with an unparsable body throws the of its +/// status instead. Mirrors the reference client's InvalidResponseBodyError. /// public sealed class InvalidResponseBodyException : ApifyClientException { diff --git a/src/Apify.Client/Exceptions/ResponseValidationException.cs b/src/Apify.Client/Exceptions/ResponseValidationException.cs index 1b37d0d..ce5d318 100644 --- a/src/Apify.Client/Exceptions/ResponseValidationException.cs +++ b/src/Apify.Client/Exceptions/ResponseValidationException.cs @@ -3,11 +3,10 @@ namespace Apify.Client.Exceptions; /// -/// Thrown when a successful response is valid JSON but does not match the schema the Apify API -/// specification declares for its operation: the data envelope or a required property is missing, a -/// value has the wrong JSON type, is null where that is not allowed, or falls outside the documented -/// format, pattern or range. Unknown properties and unknown enum values are accepted. The response arrived -/// in full, so the request is not retried. Matches the reference client's ResponseValidationError. +/// Thrown when a successful response is valid JSON but does not match the schema the API specification declares for +/// its operation: a missing envelope or required property, a wrong type, a null where it is not allowed, or a +/// value outside the documented format, pattern, or range. Unknown properties and enum values are accepted. The response +/// arrived in full, so it is not retried. Mirrors the reference client's ResponseValidationError. /// public sealed class ResponseValidationException : ApifyClientException { diff --git a/src/Apify.Client/Http/ApifyRequestOptions.cs b/src/Apify.Client/Http/ApifyRequestOptions.cs index 833c1dd..b968d55 100644 --- a/src/Apify.Client/Http/ApifyRequestOptions.cs +++ b/src/Apify.Client/Http/ApifyRequestOptions.cs @@ -2,22 +2,17 @@ namespace Apify.Client.Http; -/// -/// Keys of the per-request settings the client passes to an in -/// . -/// +/// Keys of the per-request settings the client passes to an in . /// -/// A custom transport has to honour them, or it can send the API token somewhere it must not go. The built-in +/// A custom transport has to honor them, or it can send the API token where it must not go. The built-in /// does. /// public static class ApifyRequestOptions { /// - /// Set to false on a request that must not follow redirects: the transport returns a redirect - /// response as it is. The client sets it on requests to an Actor's server (see - /// ), because they carry the API token in a header that an - /// automatic redirect would send to a host the Actor's code picked. A request without the option follows - /// redirects as usual. + /// Set to false on a request that must not follow redirects; the transport returns a redirect response as it is. + /// The client sets it on requests to an Actor's server (see ), which carry the + /// token in a header that a redirect would send to a host the Actor's code picked. Without it, redirects are followed. /// public static readonly HttpRequestOptionsKey FollowRedirects = new("Apify.FollowRedirects"); } diff --git a/src/Apify.Client/Http/GzipHttpCompressor.cs b/src/Apify.Client/Http/GzipHttpCompressor.cs index c87c37e..cfbaa3f 100644 --- a/src/Apify.Client/Http/GzipHttpCompressor.cs +++ b/src/Apify.Client/Http/GzipHttpCompressor.cs @@ -16,9 +16,8 @@ public sealed class GzipHttpCompressor : HttpCompressor /// Creates a gzip compressor. /// - /// The compression level. .NET exposes zlib's levels as three named values: - /// is level 1, level 6 (the default, as in the reference client) and - /// level 9. + /// The compression level. is zlib level 1, + /// level 6 (the default, as in the reference client), and level 9. /// /// is not a defined . public GzipHttpCompressor(CompressionLevel level = CompressionLevel.Optimal) diff --git a/src/Apify.Client/Http/HttpClientTransport.cs b/src/Apify.Client/Http/HttpClientTransport.cs index bb7fb6f..1164df7 100644 --- a/src/Apify.Client/Http/HttpClientTransport.cs +++ b/src/Apify.Client/Http/HttpClientTransport.cs @@ -7,18 +7,15 @@ namespace Apify.Client.Http; -/// -/// The default , backed by . -/// +/// The default , backed by . /// -/// The per-attempt timeout is applied to each request by the orchestrating client via a linked -/// cancellation token, so the shared is left infinite. Non-2xx statuses -/// are returned as normal responses; only connection/timeout failures are thrown as +/// The client applies the per-attempt timeout with a linked cancellation token, so is +/// infinite. Non-2xx statuses are returned as responses; connection and timeout failures are thrown as /// . /// public sealed class HttpClientTransport : IHttpTransport, IDisposable { - /// Connection-establishment timeout (distinct from the per-request timeout the client applies). + /// Connection-establishment timeout. private static readonly TimeSpan ConnectTimeout = TimeSpan.FromSeconds(30); private readonly HttpClient _httpClient; @@ -27,30 +24,27 @@ public sealed class HttpClientTransport : IHttpTransport, IDisposable private readonly bool _ownsNoRedirectClient; /// - /// Creates a transport, optionally wrapping caller-supplied s (e.g. ones - /// configured with a proxy or custom TLS). For any client that is not supplied, an internal one is created - /// and disposed with this instance. + /// Creates a transport, optionally wrapping caller-supplied s, for example ones configured with + /// a proxy or custom TLS. A client that is not supplied is created and disposed with this instance. /// /// - /// A request that must not follow redirects (see , used for - /// requests to an Actor's server) is sent through the no-redirect client. A client supplied as - /// cannot be switched to that mode, so supply a second one with automatic - /// redirects disabled as , configured like the first one otherwise; - /// without it, such a request throws rather than risk following a - /// redirect with the API token. + /// A request that must not follow redirects (, used for requests to an + /// Actor's server) goes through the no-redirect client. A supplied cannot be switched to + /// that mode, so also supply , configured like it but with automatic redirects + /// disabled. Without it, such a request throws rather than follow a redirect + /// with the API token. /// /// An optional pre-configured HTTP client to use. /// - /// An optional pre-configured HTTP client whose handler does not follow redirects, used for requests that - /// must not follow them. Required for those requests when is supplied. + /// An optional pre-configured HTTP client that does not follow redirects. Required for requests that must not follow + /// them when is supplied. /// public HttpClientTransport(HttpClient? httpClient = null, HttpClient? noRedirectHttpClient = null) { _httpClient = httpClient ?? CreateOwnedClient(allowAutoRedirect: true); _ownsClient = httpClient is null; - // A transport that owns its main client can create the matching no-redirect one; one wrapping a - // caller's client has to be given it. + // An owned main client gets a matching no-redirect one; a caller's client has to be given it. _noRedirectClient = noRedirectHttpClient ?? (httpClient is null ? CreateOwnedClient(allowAutoRedirect: false) : null); _ownsNoRedirectClient = noRedirectHttpClient is null && httpClient is null; } @@ -61,13 +55,11 @@ private static HttpClient CreateOwnedClient(bool allowAutoRedirect) { ConnectTimeout = ConnectTimeout, AllowAutoRedirect = allowAutoRedirect, - // The API advertises brotli/gzip/deflate response compression on the dataset-items and - // key-value-store record endpoints. Enabling automatic decompression makes the handler send - // the matching `Accept-Encoding` request header and transparently inflate the response, so - // compressed payloads are handled the same way as in the reference JS client. + // The API compresses responses of the dataset-items and record endpoints. Automatic decompression sends the matching + // `Accept-Encoding` and inflates the response, like the reference client. .NET has no zstd decompression, so zstd is not advertised. AutomaticDecompression = DecompressionMethods.Brotli | DecompressionMethods.GZip | DecompressionMethods.Deflate, }; - // The client-side retry orchestrator owns the per-request timeout, so disable HttpClient's own. + // The client's retry loop owns the per-request timeout. return new HttpClient(handler) { Timeout = Timeout.InfiniteTimeSpan }; } diff --git a/src/Apify.Client/Http/HttpCompressor.cs b/src/Apify.Client/Http/HttpCompressor.cs index 53d2bd5..cfaa104 100644 --- a/src/Apify.Client/Http/HttpCompressor.cs +++ b/src/Apify.Client/Http/HttpCompressor.cs @@ -3,16 +3,13 @@ namespace Apify.Client.Http; -/// -/// Strategy for compressing HTTP request bodies. -/// +/// Strategy for compressing HTTP request bodies. /// -/// Derive from this class to plug in a custom compressor through : -/// set to the value sent in the Content-Encoding header and implement -/// . The client calls it only for bodies that are large enough to benefit from -/// compression and whose content type does not already carry its own compression, and never for a body the -/// caller already sent with a Content-Encoding of its own. The Apify API accepts the gzip, -/// br, deflate and identity encodings. Matches the reference client's HttpCompressor. +/// Derive from this class and pass it as : set +/// to the Content-Encoding value and implement . The client calls it only for large +/// bodies whose content type has no compression of its own, and never for a body the caller sent with a +/// Content-Encoding. The API accepts gzip, br, zstd, deflate, and identity. +/// Matches the reference client's HttpCompressor. /// public abstract class HttpCompressor { diff --git a/src/Apify.Client/Http/IHttpTransport.cs b/src/Apify.Client/Http/IHttpTransport.cs index c6d7477..c994650 100644 --- a/src/Apify.Client/Http/IHttpTransport.cs +++ b/src/Apify.Client/Http/IHttpTransport.cs @@ -5,43 +5,35 @@ namespace Apify.Client.Http; -/// -/// The replaceable transport contract of the client. -/// +/// The replaceable transport of the client. /// /// -/// Implementations are responsible only for sending a single, fully-prepared -/// and returning the raw . Authentication, the User-Agent header, -/// retries and (de)serialization are handled by the client, so a backend only needs to perform one -/// network round-trip. +/// An implementation sends one fully prepared and returns the raw +/// . The client handles authentication, User-Agent, retries and +/// (de)serialization. /// /// -/// A non-2xx HTTP status is not an error at this layer — return it as a normal response. Only -/// transport-level failures (connection refused, DNS, timeout) should be thrown, as an -/// . +/// A non-2xx status is not an error at this layer: return it as a response. Throw only transport failures (connection +/// refused, DNS, timeout), as an . /// /// -/// A request that sets to false in +/// A request with set to false in /// carries the API token in a header that must not follow a redirect to -/// another host, so the implementation has to return a redirect response as it is. The client sets it on -/// requests to an Actor's server. +/// another host, so return a redirect response as it is. The client sets it on requests to an Actor's server. /// /// -/// Swap the default implementation () via -/// to share a connection pool, customize -/// TLS/proxy settings, or inject a mock in tests. +/// Replace the default through +/// to share a connection pool, configure TLS or a proxy, or mock the API in tests. /// /// public interface IHttpTransport { - /// - /// Sends a single request with a per-attempt timeout and returns the response. - /// - /// The fully-prepared request (headers and body already set). - /// The per-attempt timeout budget. + /// Sends one request with a per-attempt timeout and returns the response. + /// The prepared request. + /// The per-attempt timeout. /// - /// When true, return as soon as the response headers arrive so the body can be consumed as a - /// live stream (used by log streaming); otherwise the whole response may be buffered. + /// When true, return as soon as the headers arrive so the body can be read as a live stream; otherwise the + /// response may be buffered. /// /// A token to cancel the request. Task SendAsync( diff --git a/src/Apify.Client/Http/ZstdHttpCompressor.cs b/src/Apify.Client/Http/ZstdHttpCompressor.cs new file mode 100644 index 0000000..6d45151 --- /dev/null +++ b/src/Apify.Client/Http/ZstdHttpCompressor.cs @@ -0,0 +1,50 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using ZstdSharp; + +namespace Apify.Client.Http; + +/// +/// Compresses request bodies with zstd (Content-Encoding: zstd). Matches the reference client's +/// ZstdHttpCompressor. +/// +public sealed class ZstdHttpCompressor : HttpCompressor +{ + /// The lowest valid zstd quality: the fastest, with the least compression. + public const int MinQuality = 1; + + /// + /// The highest valid zstd quality. zstd itself goes up to 22, but levels 20 to 22 use a window above the 8 MB + /// that RFC 9659 requires HTTP decoders to support, so a record stored at those levels could fail to decode. + /// + public const int MaxQuality = 19; + + /// zstd's own default: fast, with a ratio close to the best gzip level. + public const int DefaultQuality = 3; + + private readonly int _quality; + + /// Creates a zstd compressor. + /// The compression level, from (the fastest) to (the best compression). + /// is outside the valid range. + public ZstdHttpCompressor(int quality = DefaultQuality) + { + ArgumentOutOfRangeException.ThrowIfLessThan(quality, MinQuality); + ArgumentOutOfRangeException.ThrowIfGreaterThan(quality, MaxQuality); + _quality = quality; + } + + /// + public override string ContentEncoding => "zstd"; + + /// + public override Task CompressAsync(byte[] data, CancellationToken cancellationToken = default) + => Task.Run(() => Compress(data), cancellationToken); + + private byte[] Compress(byte[] data) + { + using var compressor = new Compressor(_quality); + return compressor.Wrap(data).ToArray(); + } +} diff --git a/src/Apify.Client/Internal/HttpClientCore.cs b/src/Apify.Client/Internal/HttpClientCore.cs index e669417..d4fe116 100644 --- a/src/Apify.Client/Internal/HttpClientCore.cs +++ b/src/Apify.Client/Internal/HttpClientCore.cs @@ -14,26 +14,26 @@ namespace Apify.Client.Internal; /// -/// The orchestrating HTTP client shared by every resource client. It owns the transport, the optional -/// API token, the User-Agent, and the retry/timeout policy, and applies them to every request. +/// The HTTP client shared by every resource client. It owns the transport, token, User-Agent, and the +/// retry and timeout policy, and applies them to every request. /// internal sealed class HttpClientCore { - /// Status returned when the per-resource rate limit is hit. + /// Status of a rate limited request. public const int RateLimitExceeded = 429; - /// Statuses at or above this value are treated as retryable internal server errors. + /// Statuses at or above this value are retried as server errors. private const int MinServerError = 500; - /// Responses with a status below this value are treated as success. + /// Statuses below this value are a success. public const int MaxSuccessStatus = 300; - /// Exponential-backoff multiplier applied to the inter-retry delay after each attempt. + /// Multiplier of the delay between retries. private const int BackoffFactor = 2; private const int NotFound = 404; - /// Prefix of the headers that describe a request body; they belong to the body, not to the request. + /// Prefix of the headers that describe a request body. private const string ContentHeaderPrefix = "Content-"; /// The header that carries the media type of a request body. @@ -42,19 +42,16 @@ internal sealed class HttpClientCore /// The header that carries the encoding (compression) of a request body. public const string ContentEncodingHeader = "Content-Encoding"; - /// Content headers the client derives from the body itself, so a caller-supplied value is ignored. + /// Content headers the client derives from the body, so a caller-supplied value is ignored. private static readonly string[] BodyDerivedContentHeaders = { ContentTypeHeader, ContentEncodingHeader, "Content-Length" }; - /// - /// Request bodies whose size in bytes is at or above this threshold are compressed before sending, - /// matching the reference client's minimum-compression size. - /// + /// Bodies of at least this many bytes are compressed, as in the reference client. private const int MinCompressBytes = 1024; - /// Media type prefixes whose payloads carry their own compression (e.g. image/png). + /// Media type prefixes whose payloads carry their own compression. private static readonly string[] AlreadyCompressedMediaTypePrefixes = { "audio/", "image/", "video/" }; - /// Exact media types whose payloads carry their own compression (archives, packages, fonts). + /// Media types whose payloads carry their own compression (archives, packages, fonts). private static readonly HashSet AlreadyCompressedMediaTypes = new(StringComparer.Ordinal) { "application/epub+zip", @@ -78,7 +75,7 @@ internal sealed class HttpClientCore "font/woff2", }; - /// Uncompressed media types that sit under an already-compressed prefix but still benefit from compression. + /// Media types under an already-compressed prefix that are uncompressed and still benefit from compression. private static readonly HashSet CompressibleMediaTypes = new(StringComparer.Ordinal) { "audio/aiff", @@ -99,7 +96,7 @@ internal sealed class HttpClientCore "image/x-ms-bmp", }; - /// Structured syntax suffixes marking a media type as text even under an already-compressed prefix (e.g. image/svg+xml). + /// Suffixes that mark a media type as text even under an already-compressed prefix, such as image/svg+xml. private static readonly string[] CompressibleMediaTypeSuffixes = { "+json", "+xml" }; private readonly IHttpTransport _transport; @@ -131,7 +128,7 @@ public HttpClientCore( /// The request statistics shared with . public Statistics Stats => _stats; - /// The User-Agent header value this client sends (a User-Agent default header wins over the built-in one). + /// The User-Agent this client sends; a User-Agent default header wins over the built-in one. public string UserAgent { get; } private static string? FindHeader(IReadOnlyDictionary? headers, string name) @@ -152,14 +149,13 @@ public HttpClientCore( return null; } - /// The configured overall per-request timeout budget, in seconds. + /// The overall per-request timeout budget, in seconds. public double RequestTimeoutSecs => _retry.TimeoutSecs; /// - /// Sends a request with auth, User-Agent and the retry policy applied, returning the successful - /// response (the caller owns and disposes it). A other than - /// changes how the call authenticates, which responses it retries and - /// whether an error response is returned instead of thrown. + /// Sends a request with auth, User-Agent, and the retry policy, and returns the successful response, which the + /// caller disposes. A other than changes the + /// authentication, the retried responses, and whether an error response is returned instead of thrown. /// public async Task CallAsync( HttpMethod method, @@ -180,17 +176,13 @@ public async Task CallAsync( var delayMillis = _retry.MinDelayMillis; var maxAttempts = _retry.MaxRetries + 1; var path = ExtractPath(url); - // An explicit timeout (e.g. a per-queue or per-call WithTimeout override) always wins as the base for - // the per-attempt doubling below; otherwise the tier's configured duration is the base. Either way, - // the retry-growth cap is always the configured overall budget (ApifyClientOptions.TimeoutSecs) — - // matching the reference client, an explicit override above that budget is capped back down to it - // rather than silently exceeding it. Raise TimeoutSecs itself to allow a longer per-call timeout. + // An explicit timeout (such as a WithTimeout override) is the base that doubles per attempt; otherwise it is the + // tier's duration. Either way the growth is capped at the overall budget (ApifyClientOptions.TimeoutSecs), as in the + // reference client. var baseTimeout = timeout ?? TimeSpan.FromSeconds(_retry.TierSecs(tier)); var overallCap = TimeSpan.FromSeconds(_retry.TimeoutSecs); - // Normalize (and, when large enough, compress) the body once up front so retries reuse the same - // prepared payload instead of re-encoding and re-compressing on every attempt. A caller-supplied - // explicitContentEncoding (e.g. SetRecordAsync's contentEncoding for an already-compressed value) - // skips this: the bytes are already in their final, encoded form and must reach the API unchanged. + // Prepare (and, when large enough, compress) the body once, so retries reuse it. An explicit content encoding skips + // this: the bytes are already encoded and must reach the API unchanged. var prepared = explicitContentEncoding is not null ? new PreparedBody(bodyBytes ?? (body is not null ? Encoding.UTF8.GetBytes(body) : null), contentType, explicitContentEncoding) : await PrepareBodyAsync(body, bodyBytes, contentType, policy, cancellationToken).ConfigureAwait(false); @@ -209,14 +201,12 @@ public async Task CallAsync( var status = (int)response.StatusCode; if (status < MaxSuccessStatus) { - // The sent request is disposed by now; keep a body-less copy of it so response readers can - // report the method and path in the errors they raise. + // The sent request is disposed; keep a body-less copy so response readers can report the method and path. response.RequestMessage = new HttpRequestMessage(method, url); return response; } - // The statistics track the API's rate limits; only API calls throw on an error status, so a call - // that returns error responses is not an API call and its 429s stay out of them. + // The statistics track the API's rate limits. Only API calls throw on an error status, so the 429s of other calls stay out. if (status == RateLimitExceeded && policy.ThrowOnErrorStatus) { _stats.AddRateLimitError(attempt); @@ -236,8 +226,7 @@ public async Task CallAsync( catch (ApifyTransportException ex) { lastError = ex; - // Network/timeout failures are retryable, unless the caller opted out of retrying timeouts or of - // retrying transport errors at all. + // Network and timeout failures are retried, unless the caller opted out of retrying timeouts or transport errors. retryable = policy.RetryTransportErrors && !(doNotRetryTimeouts && ex.IsTimeout); } @@ -251,11 +240,11 @@ await Task.Delay(TimeSpan.FromMilliseconds(RandomizedDelayMillis(delayMillis)), delayMillis = Math.Min(delayMillis * BackoffFactor, _retry.MaxDelayMillis); } - // Unreachable in practice (maxAttempts >= 1); defensive. + // Unreachable: there is at least one attempt. throw lastError ?? new ApifyTransportException("request failed with no attempts"); } - /// Opens a live streaming response (single attempt, no retry). Used by log streaming. + /// Opens a live streaming response with a single attempt and no retry, for log streaming. public Task StreamAsync(string url, CancellationToken cancellationToken) { _stats.AddCall(); @@ -278,9 +267,8 @@ private async Task SendOnceAsync( } /// - /// Rejects a default header that is not a valid request header. Content headers (Content-Type, - /// Content-Encoding, Content-Length, ...) describe the body, which the client sets per - /// request, so they cannot be defaults; any other rejected name is not a valid HTTP header name. + /// Rejects a default header that is a content header (Content-Type, Content-Length, and so on), because + /// the client sets those per request, or not a valid header name. /// /// A header name is a content header or not a valid header name. private static void ValidateDefaultHeaders(IReadOnlyDictionary? headers) @@ -307,9 +295,9 @@ private static void ValidateDefaultHeaders(IReadOnlyDictionary? } /// - /// Moves the request's Authorization header (the token, or a default header of that name) to the header - /// named ; nothing changes for null. A header of that name set for the - /// single call is applied afterwards and still wins. + /// Moves the request's Authorization header (the token or a default header of that name) to + /// ; null changes nothing. A header of that name set for the single call is + /// applied afterwards and wins. /// private static void MoveAuthorization(HttpRequestMessage request, string? headerName) { @@ -324,7 +312,7 @@ private static void MoveAuthorization(HttpRequestMessage request, string? header request.Headers.TryAddWithoutValidation(headerName, authorization); } - /// Sets each header on the request, replacing any value already set under the same name. + /// Sets each header on the request, replacing a value already set under the same name. private static void SetHeaders(HttpRequestMessage request, IReadOnlyDictionary? headers) { if (headers is null) @@ -334,7 +322,7 @@ private static void SetHeaders(HttpRequestMessage request, IReadOnlyDictionary name.StartsWith(ContentHeaderPrefix, StringComparison.OrdinalIgnoreCase); - /// Builds a fully-prepared request with auth, User-Agent, content type and extra headers. + /// Builds a request with auth, User-Agent, content type, and extra headers. private HttpRequestMessage BuildRequest( HttpMethod method, string url, @@ -362,8 +350,7 @@ private HttpRequestMessage BuildRequest( request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _token); } - // The client-wide default headers win over the built-in ones above, and a header set for this single call - // wins over both. + // Client-wide default headers win over the built-in ones, and a header of this single call wins over both. SetHeaders(request, _defaultHeaders); MoveAuthorization(request, policy.AuthorizationHeader); SetHeaders(request, extraHeaders); @@ -375,7 +362,7 @@ private HttpRequestMessage BuildRequest( if (prepared.Bytes is not null) { var content = new ByteArrayContent(prepared.Bytes); - // Set the content type verbatim (no charset appended unless the caller added one). + // Set the content type as given, without appending a charset. content.Headers.ContentType = string.IsNullOrEmpty(prepared.ContentType) ? null : MediaTypeHeaderValue.Parse(prepared.ContentType); @@ -392,9 +379,8 @@ private HttpRequestMessage BuildRequest( } /// - /// Sets the caller's content headers on the body, except the ones the client derives from the body itself. A - /// request without a body has nothing to attach them to (for example Content-Disposition), so they are - /// not sent. + /// Sets the caller's content headers on the body, except the ones derived from the body. A request without a body has + /// nothing to attach them to, so they are not sent. /// private static void AddContentHeaders(HttpContent content, IReadOnlyDictionary? headers) { @@ -416,15 +402,12 @@ private static void AddContentHeaders(HttpContent content, IReadOnlyDictionary - /// Normalizes a request body to bytes and, when it is large enough, compresses it. Raw bytes take - /// precedence so binary records (e.g. images) are used as-is rather than re-encoded through a UTF-8 - /// string; a string body is UTF-8 encoded. Either kind of payload is then compressed once it reaches - /// the size threshold (see the remarks). + /// Converts a request body to bytes and compresses it once it reaches the size threshold. Raw bytes take precedence + /// over a string, which is UTF-8 encoded. /// /// - /// Bodies at or above whose content type is compressible are compressed with - /// the configured : brotli (Content-Encoding: br) by default, matching - /// the reference client's preference. + /// Bodies of at least with a compressible content type are compressed with the + /// configured , brotli by default. /// private async Task PrepareBodyAsync(string? body, byte[]? bodyBytes, string contentType, RequestPolicy policy, CancellationToken cancellationToken) { @@ -445,17 +428,14 @@ private async Task PrepareBodyAsync(string? body, byte[]? bodyByte } /// - /// Decides whether a request body with the given content type is worth compressing, matching the - /// reference client's isCompressibleContentType(). + /// Decides whether a body with this content type is worth compressing, as the reference client's + /// isCompressibleContentType() does. /// /// - /// Images, audio, video and archives already carry their own compression: running them through - /// brotli/gzip burns CPU, holds a second full copy of the body in memory, and usually produces output - /// slightly larger than the input. Formats that are raw despite such a media type (e.g. image/bmp, - /// audio/wav) are still compressed, as are +json/+xml structured-syntax suffixes - /// (e.g. image/svg+xml). application/octet-stream is deliberately left off the - /// already-compressed list: it is the catch-all for unknown binary data and SetRecordAsync's - /// fallback when no content type is given. No content type is assumed compressible. + /// Images, audio, video, and archives already carry their own compression, so compressing them wastes CPU and memory + /// for no gain. Raw formats under such a type (image/bmp, audio/wav) and +json or +xml + /// types (image/svg+xml) are still compressed. application/octet-stream counts as compressible, since it + /// holds unknown binary data and is the fallback of SetRecordAsync. A missing content type is not compressible. /// internal static bool IsCompressibleContentType(string? contentType) { @@ -464,7 +444,7 @@ internal static bool IsCompressibleContentType(string? contentType) return true; } - // Content-Type may carry parameters, e.g. "text/plain; charset=utf-8". + // The content type may carry parameters, such as "; charset=utf-8". var semicolon = contentType.IndexOf(';', StringComparison.Ordinal); var mediaType = (semicolon >= 0 ? contentType.Substring(0, semicolon) : contentType) .Trim() @@ -499,19 +479,13 @@ internal static bool IsCompressibleContentType(string? contentType) return true; } - /// - /// A request body normalized to bytes, together with the content type and optional - /// Content-Encoding to send. A value carries no body. - /// + /// A request body as bytes, with the content type and optional Content-Encoding to send. private readonly record struct PreparedBody(byte[]? Bytes, string ContentType, string? ContentEncoding); /// - /// Returns min(overall, base * 2^(attempt-1)): the first attempt uses - /// as given (it is not pre-capped by the caller — capping happens only here); each retry doubles it (a - /// slow-but-progressing connection gets more time), and every attempt, including the first, is capped at - /// — the configured overall budget (ApifyClientOptions.TimeoutSecs). - /// This is what keeps an explicit WithTimeout override above that budget from exceeding it, - /// matching the reference client's timeoutMaxSecs cap. + /// Returns min(overall, base * 2^(attempt-1)): each retry doubles the timeout, and every attempt, the first + /// included, is capped at the overall budget (ApifyClientOptions.TimeoutSecs), as in the reference client's + /// timeoutMaxSecs. /// private static TimeSpan AttemptTimeout(TimeSpan baseTimeout, TimeSpan overall, int attempt) { @@ -533,7 +507,7 @@ private static bool IsStatusRetryable(int status, RequestPolicy policy) ? codes.Contains(status) : status == RateLimitExceeded || status >= MinServerError; - /// Returns a delay chosen randomly from [delay, 2*delay) (exponential backoff + jitter). + /// Returns a random delay from [delay, 2*delay): exponential backoff with jitter. private static double RandomizedDelayMillis(double delayMillis) { if (delayMillis <= 0) @@ -544,7 +518,7 @@ private static double RandomizedDelayMillis(double delayMillis) return delayMillis + (Random.Shared.NextDouble() * delayMillis); } - /// Builds an from an API error response body. + /// Builds an from an error response body. public static ApifyApiException BuildApiError(int status, string body, int attempt, string method, string path) { string? type = null; @@ -583,7 +557,7 @@ public static ApifyApiException BuildApiError(int status, string body, int attem return null; } - /// Returns the path+query portion of a URL, for error reporting. + /// Returns the path and query of a URL, for error messages. public static string ExtractPath(string url) { var rest = url; @@ -597,7 +571,7 @@ public static string ExtractPath(string url) return slash >= 0 ? rest.Substring(slash) : string.Empty; } - /// Reports whether an exception represents a "resource not found" API error. + /// Whether an exception is a "not found" API error. public static bool IsNotFound(Exception ex) { if (ex is not ApifyApiException apiError || apiError.StatusCode != NotFound) diff --git a/src/Apify.Client/Internal/Json.cs b/src/Apify.Client/Internal/Json.cs index 4b4288f..e7adba1 100644 --- a/src/Apify.Client/Internal/Json.cs +++ b/src/Apify.Client/Internal/Json.cs @@ -4,15 +4,10 @@ namespace Apify.Client.Internal; -/// -/// Shared JSON (de)serialization for the client. -/// +/// JSON serialization shared by the client. internal static class Json { - /// - /// Serialization options matching the reference client's output: slashes and non-ASCII characters - /// are left unescaped (like PHP's JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE). - /// + /// Serialization options of the reference client's output: slashes and non-ASCII characters are not escaped. private static readonly JsonSerializerOptions SerializerOptions = new() { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, @@ -32,9 +27,8 @@ public static string Encode(JsonNode? value) => value?.ToJsonString(SerializerOptions) ?? "null"; /// - /// Decodes a JSON string into a (null for an empty body). Throws - /// when it is not valid JSON; response bodies go through - /// , which turns that into an . + /// Decodes a JSON string into a (null for an empty body). Throws + /// for invalid JSON; turns that into an . /// public static JsonNode? Decode(string body) { diff --git a/src/Apify.Client/Internal/JsonValues.cs b/src/Apify.Client/Internal/JsonValues.cs index 2893168..995b2a4 100644 --- a/src/Apify.Client/Internal/JsonValues.cs +++ b/src/Apify.Client/Internal/JsonValues.cs @@ -5,10 +5,7 @@ namespace Apify.Client.Internal; -/// -/// Small helpers for reading typed values out of a decoded with fallbacks, -/// used by the page/head models. Absent or mistyped fields fall back rather than throwing. -/// +/// Reads typed values out of a , falling back instead of throwing on absent or mistyped fields. internal static class JsonValues { /// The object as a , or an empty object if it is not one. diff --git a/src/Apify.Client/Internal/PageSizing.cs b/src/Apify.Client/Internal/PageSizing.cs index a438bd4..8011cc9 100644 --- a/src/Apify.Client/Internal/PageSizing.cs +++ b/src/Apify.Client/Internal/PageSizing.cs @@ -3,9 +3,8 @@ namespace Apify.Client.Internal; /// -/// Shared handling of the client-side ChunkSize iteration option: the maximum number of items -/// requested per API call while iterating across pages. It never reaches the query string itself; it only -/// sizes each page's limit, matching the reference client's chunkSize. +/// Handling of the client-side ChunkSize iteration option, the most items requested per API call. It only sizes +/// each page's limit and is never sent itself, as in the reference client. /// internal static class PageSizing { @@ -21,9 +20,8 @@ public static void ValidateChunkSize(int? chunkSize, string optionName) } /// - /// The smaller of two page limits, for the limit query parameter. The API reads a limit of - /// 0 as unset, so 0 (like null) means "no limit" here and loses to any other value; - /// the result is null when neither is set. Matches the reference client's minForLimitParam. + /// The smaller of two page limits. The API reads 0 as unset, so 0 and null mean "no limit" and lose + /// to any other value; the result is null when neither is set. As the reference client's minForLimitParam. /// public static int? MinForLimit(int? a, int? b) { diff --git a/src/Apify.Client/Internal/PagedIteration.cs b/src/Apify.Client/Internal/PagedIteration.cs index 4cf97a6..e245588 100644 --- a/src/Apify.Client/Internal/PagedIteration.cs +++ b/src/Apify.Client/Internal/PagedIteration.cs @@ -6,40 +6,30 @@ namespace Apify.Client.Internal; -/// One fetched page: its items and, when the API reports it, the number of rows it scanned. +/// A fetched page: its items and, when the API reports it, the number of rows it scanned. /// The items on the page. -/// The rows the API scanned to produce the page (null when not reported). +/// The rows the API scanned for the page (null when not reported). /// The item type. internal readonly record struct FetchedPage(IReadOnlyList Items, long? ScannedRows); -/// -/// The offset/limit paging loop shared by every list iterator, ported from the reference client's -/// listPaginatedFromCallback. -/// +/// The offset/limit paging loop shared by the list iterators, ported from the reference client's listPaginatedFromCallback. internal static class PagedIteration { - /// - /// Lazily yields every item from on, fetching pages on demand. - /// + /// Lazily yields every item from on, fetching pages on demand. /// - /// caps the items yielded, or the rows scanned when - /// is set (null or 0 for no cap); a negative offset or - /// limit is rejected. - /// The next offset and the stop condition follow the rows a page scanned, not the items it returned: - /// advancing by the item count would re-scan rows after a filter dropped some and skip rows after - /// unwind multiplied them, and stopping at an empty page would end the iteration in front of rows - /// a filter hid. The scanned count is derived from a dataset's item count, which lags a fresh push, so - /// the larger of it and the item count is used, capped at the rows the request asked for so an unwound - /// page does not advance past rows the next request would then never read. The total a page reports is - /// never consulted, because it changes when the listed resource grows during the iteration; iteration - /// stops once a page scans no rows, which costs one extra request at the end. + /// caps the items yielded, or the rows scanned with + /// (null or 0 for no cap). A negative offset or limit is rejected. The next offset and the stop condition + /// follow the rows a page scanned, not the items it returned: the item count would re-scan rows after a filter dropped + /// some, skip rows after unwind multiplied them and stop in front of rows a filter hid. The scanned count lags a + /// fresh push, so the larger of it and the item count is used, capped at the requested rows. The reported total is + /// ignored because it changes while the resource grows. Iteration stops once a page scans no rows, which costs one + /// extra request. /// /// The offset of the first row. /// The most items (or rows, see ) across all pages, or null/0 for no cap. /// - /// Whether counts the rows scanned (dataset items) rather than the items - /// returned (listings, where the API can scan more rows than it returns, e.g. the Store dropping Actors - /// the caller cannot run). + /// Whether counts the rows scanned (dataset items) rather than the items returned + /// (listings, where the API can scan more rows than it returns, such as the Store dropping Actors the caller cannot run). /// /// The most rows to request per call, or null for the API default. /// Fetches one page given its offset and limit (null limit: leave it out). diff --git a/src/Apify.Client/Internal/QueryParams.cs b/src/Apify.Client/Internal/QueryParams.cs index 6307fa7..b3d1e46 100644 --- a/src/Apify.Client/Internal/QueryParams.cs +++ b/src/Apify.Client/Internal/QueryParams.cs @@ -5,10 +5,7 @@ namespace Apify.Client.Internal; -/// -/// An ordered collection of query parameters that omits absent (null) values and encodes -/// booleans as 1/0, matching the Apify API conventions. -/// +/// An ordered set of query parameters that omits null values and encodes booleans as 1/0. internal sealed class QueryParams { private readonly List> _pairs = new(); @@ -48,10 +45,7 @@ public QueryParams AddDouble(string key, double? value) return this; } - /// - /// Adds a boolean parameter, encoded as 1/0, if the value is non-null. This matches the - /// JS reference client, whose axios paramsSerializer converts booleans via Number(value). - /// + /// Adds a boolean as 1/0, like the reference client's paramsSerializer, if the value is non-null. public QueryParams AddBool(string key, bool? value) { if (value is not null) @@ -80,11 +74,7 @@ public QueryParams AddRaw(string key, string value) return this; } - /// - /// Sets an integer parameter, replacing any existing occurrences of . Used by - /// auto-paging iteration to overwrite the per-page offset/limit without emitting the - /// parameter twice. - /// + /// Sets an integer parameter, replacing existing ones, so iteration can overwrite the per-page offset and limit. public QueryParams Set(string key, long value) { _pairs.RemoveAll(pair => string.Equals(pair.Key, key, StringComparison.Ordinal)); @@ -92,10 +82,7 @@ public QueryParams Set(string key, long value) return this; } - /// - /// Returns a copy with the page's offset and limit set, the latter left out when - /// is null (so a caller's own limit does not leak into every page). - /// + /// Returns a copy with the page's offset and limit; a null limit is left out, so a caller's limit does not leak into every page. public QueryParams PageQuery(int offset, int? limit) { var page = Copy().Set("offset", offset).Remove("limit"); diff --git a/src/Apify.Client/Internal/RequestPolicy.cs b/src/Apify.Client/Internal/RequestPolicy.cs index aa159f0..6f4698d 100644 --- a/src/Apify.Client/Internal/RequestPolicy.cs +++ b/src/Apify.Client/Internal/RequestPolicy.cs @@ -4,9 +4,9 @@ namespace Apify.Client.Internal; /// -/// Per-request overrides of the shared HTTP behaviour, for calls that are not plain API calls. The default -/// instance is the behaviour of every API call; overrides it to -/// talk to an Actor's own server (the reference client's per-request ApifyRequestConfig options). +/// Per-request overrides of the shared HTTP behavior for calls that are not plain API calls. The default is the +/// behavior of an API call; overrides it to call an Actor's server, as the +/// reference client's per-request ApifyRequestConfig does. /// internal sealed class RequestPolicy { @@ -14,16 +14,14 @@ internal sealed class RequestPolicy public static readonly RequestPolicy Default = new(); /// - /// The header that carries the API token, or null for the standard Authorization header. Actor - /// servers accept X-Apify-Authorization, which the platform removes before the request reaches the - /// Actor's code, so Authorization stays free for the Actor's own authentication. + /// The header that carries the token, or null for Authorization. Actor servers accept + /// X-Apify-Authorization, which the platform removes, so Authorization stays free for the Actor's own authentication. /// public string? AuthorizationHeader { get; init; } /// - /// Whether an error status throws . When false, the error - /// response is returned once no retry follows, and a rate limit (429) is not counted in the client's - /// statistics, which track the API's rate limits. + /// Whether an error status throws . When false, the error response is + /// returned once no retry follows, and a 429 is not counted in the client's statistics of the API's rate limits. /// public bool ThrowOnErrorStatus { get; init; } = true; @@ -31,15 +29,14 @@ internal sealed class RequestPolicy public IReadOnlyCollection? RetryStatusCodes { get; init; } /// - /// Whether transport errors, timeouts included, are retried. When false, the first one fails the call, - /// since the request may have reached the server before it failed. + /// Whether transport errors, timeouts included, are retried. When false, the first one fails the call, since the + /// request may have reached the server. /// public bool RetryTransportErrors { get; init; } = true; /// - /// Whether the transport follows redirects. When false, a redirect response is returned like an error - /// status (see ), so a header that carries the token never - /// goes to a host the server picked. + /// Whether the transport follows redirects. When false, a redirect is returned like an error status (see + /// ), so the token never goes to a host the server picked. /// public bool FollowRedirects { get; init; } = true; diff --git a/src/Apify.Client/Internal/RequestTier.cs b/src/Apify.Client/Internal/RequestTier.cs index b85c84c..a99a5db 100644 --- a/src/Apify.Client/Internal/RequestTier.cs +++ b/src/Apify.Client/Internal/RequestTier.cs @@ -1,11 +1,8 @@ namespace Apify.Client.Internal; /// -/// The timeout tier a request falls into, matching the reference client's TimeoutTier -/// ('short'/'medium'/'long'): each tier's duration is configured once on -/// and every method that sends a request picks the tier that -/// fits its expected duration, so the default wait before giving up is consistent across the client instead -/// of a single one-size-fits-all budget. +/// The timeout tier of a request, as the reference client's TimeoutTier. Each tier's duration is configured on +/// , and each method picks the tier that fits its expected duration. /// internal enum RequestTier { diff --git a/src/Apify.Client/Internal/ResourceContext.cs b/src/Apify.Client/Internal/ResourceContext.cs index c3ae434..049c01c 100644 --- a/src/Apify.Client/Internal/ResourceContext.cs +++ b/src/Apify.Client/Internal/ResourceContext.cs @@ -11,8 +11,7 @@ namespace Apify.Client.Internal; /// -/// The resolved context for a resource client: its base URL and the shared HTTP client. The methods -/// here implement the CRUD primitives once, so each resource client stays small and consistent (DRY). +/// The base URL and shared HTTP client of a resource client, with the CRUD primitives the resource clients build on. /// internal sealed class ResourceContext { @@ -28,28 +27,24 @@ internal sealed class ResourceContext /// How long to wait between polls while waiting for a run/build to finish, in seconds. private const double WaitPollIntervalSecs = 0.25; - /// Server-side waitForFinish chunk size (the API caps server waiting at 60 seconds). + /// Longest server-side waitForFinish the API accepts, in seconds. private const int WaitRequestSecs = 60; /// - /// Safety margin subtracted from the configured per-request timeout when choosing the server-side - /// waitForFinish value, so the server responds before the client's socket timeout fires. + /// Subtracted from the request timeout when choosing the server-side waitForFinish, so the server answers + /// before the client times out. /// private const int WaitTimeoutMarginSecs = 5; /// - /// Finite upper bound used when the caller asks to wait "indefinitely" (waitSecs == null). The - /// API will not accept "Infinity" and an unbounded loop can spin forever on a transient 404; 999999s - /// (~11.5 days) is effectively indefinite while guaranteeing termination. + /// Upper bound for waiting indefinitely (waitSecs is null), about 11.5 days: the API rejects "Infinity", + /// and an unbounded loop could spin forever on a transient 404. /// private const int MaxWaitForFinishSecs = 999999; /// - /// How long keeps polling a resource that keeps answering 404 (to ride - /// out database replica lag just after creation) before giving up and throwing, instead of running out - /// the full wait budget. Matches the reference client's MAX_WAIT_WHEN_JOB_NOT_EXIST_MILLIS — a - /// job that never existed (e.g. a typo'd run id) would otherwise hang for up to - /// (~11.5 days) when waitSecs is left unset. + /// How long keeps polling a resource that keeps answering 404 (replica lag right + /// after creation) before it reports the resource as missing. As in the reference client. /// private const int MaxWaitWhenJobNotExistMillis = 3000; @@ -80,10 +75,9 @@ private ResourceContext(HttpClientCore http, string url, string baseUrl) public TimeSpan? RequestTimeout => _requestTimeout; /// - /// Creates a context for a collection endpoint: {base}/{resourcePath}. Any - /// are copied into so they are applied to - /// every call on the nested resource (used to forward the last-run status/origin filters - /// to a run's nested storage and log clients, matching the reference client). + /// Creates a context for a collection endpoint, {base}/{resourcePath}. are + /// applied to every call, which is how a run's nested storages and log get the last-run status and + /// origin filters. /// public static ResourceContext Collection(HttpClientCore http, string baseUrl, string resourcePath, QueryParams? inheritedParams = null) { @@ -97,12 +91,10 @@ public static ResourceContext Single(HttpClientCore http, string baseUrl, string => new(http, baseUrl + "/" + resourcePath + "/" + ToSafeId(id, paramName), baseUrl); /// - /// Creates a context for a resource addressed without its own id: a run/build's nested default dataset, - /// key-value store, request queue or log ({base}/{resourcePath}, same shape as - /// ). A 404 through / - /// on such a context is ambiguous — it could mean the parent resource (e.g. the run) or the nested one is - /// missing — so those methods propagate it instead of resolving to null, matching the reference - /// client's catchNotFoundForResourceOrThrow() distinction between id-addressed and chained clients. + /// Creates a context for a resource addressed without its own id, such as a run's default dataset or log. A 404 for + /// such a resource is ambiguous (the parent or the nested resource may be missing), so + /// and propagate it. The reference client makes the + /// same distinction. /// public static ResourceContext NestedSingleton(HttpClientCore http, string baseUrl, string resourcePath, QueryParams? inheritedParams = null) { @@ -145,14 +137,9 @@ public string PublicUrl(string subPath) /// Merges the inherited base params with per-call params. public QueryParams MergedParams(QueryParams? p) => BaseParams.Copy().Extend(p); - // ---- CRUD primitives ------------------------------------------------------ - /// - /// GET a single resource, returning its decoded data. A 404 resolves to null, unless - /// addresses this context's own resource (empty) and the context was - /// built with — then the 404 is ambiguous (it could mean the parent - /// resource or the nested one is missing) and propagates instead. A non-empty - /// (e.g. a request looked up by its own id) is never ambiguous regardless of how this context was built. + /// GET a single resource and return its decoded data. A 404 resolves to null, except for the context's + /// own resource (empty ) on a , where it is ambiguous and propagates. /// public async Task GetResourceAsync(string subPath, QueryParams p, RequestTier tier, CancellationToken ct) { @@ -188,10 +175,8 @@ public async Task UpdateResourceAsync(string subPath, object? body, } /// - /// Performs a DELETE. A not-found is treated as a successful no-op, unless - /// addresses this context's own resource (empty) and the context was built with - /// — then the 404 is ambiguous (the parent resource or the nested one could - /// be missing) and propagates instead. + /// DELETE a resource. A 404 is a no-op, except for the context's own resource (empty ) on a + /// , where it is ambiguous and propagates. /// public async Task DeleteResourceAsync(string subPath, RequestTier tier, CancellationToken ct) { @@ -220,11 +205,9 @@ public async Task> ListResourceAsync(string subPath, QueryP } /// - /// Lazily iterates every item of an offset/limit-paginated listing, fetching pages on demand (see - /// ). It starts at ; - /// caps the total number of items (null or 0 for no cap) and - /// sizes each page request. carries the caller's filters; its - /// offset/limit are replaced per page. + /// Lazily iterates an offset/limit-paginated listing, fetching pages on demand. caps the + /// total number of items (null or 0 for no cap), sizes each page and + /// carries the filters. /// public IAsyncEnumerable IterateListAsync( string subPath, @@ -258,8 +241,7 @@ public async Task CreateResourceAsync(QueryParams p, object? body, R } /// - /// POST that gets-or-creates a named resource (POST {collection}?name=...). An optional - /// is sent as {"schema": ...}, matching the reference client. + /// POST that gets or creates a named resource (POST {collection}?name=...), with an optional schema. /// public async Task GetOrCreateNamedAsync(string? name, JsonNode? schema, CancellationToken ct) { @@ -277,9 +259,8 @@ public async Task GetOrCreateNamedAsync(string? name, JsonNode? sche } /// - /// POST with an optional raw body and content type, unwrapping the data envelope. Pass - /// instead of to send raw bytes (e.g. an Actor's raw - /// byte input, see ) without re-encoding them as a JSON string. + /// POST with an optional raw body, returning the decoded data. sends raw bytes + /// instead of . /// public async Task PostWithBodyAsync(string subPath, QueryParams p, string? body, string contentType, RequestTier tier, CancellationToken ct, byte[]? bodyBytes = null) { @@ -289,10 +270,8 @@ public async Task PostWithBodyAsync(string subPath, QueryParams p, s } /// - /// POST with a raw body, parsing the response directly without unwrapping a data envelope. - /// Used by endpoints (e.g. actor input validation) whose response is a plain object. Pass - /// instead of to send raw bytes (see - /// ) without re-encoding them as a JSON string. + /// POST with an optional raw body, returning the response without unwrapping a data envelope, for endpoints + /// (such as Actor input validation) that answer with a plain object. /// public async Task PostWithBodyNoEnvelopeAsync(string subPath, QueryParams p, string? body, string contentType, RequestTier tier, CancellationToken ct, byte[]? bodyBytes = null) { @@ -302,9 +281,7 @@ public async Task PostWithBodyAsync(string subPath, QueryParams p, s } /// - /// Encodes an Actor/task run input for the request body: raw bytes (e.g. a pre-serialized or binary - /// payload, typically paired with an explicit content type) are sent as-is, and anything else is - /// JSON-encoded, matching the reference client's ActorInput (a plain object/array, or raw bytes). + /// Encodes a run input: byte[] is sent as it is and anything else as JSON, as in the reference client. /// /// The input value, or null for no input. /// The JSON-encoded body, or null when is used. @@ -322,7 +299,7 @@ public static void EncodeInputBody(object? input, out string? body, out byte[]? bodyBytes = null; } - /// DELETE with a JSON body (used for batch request deletion), unwrapping the data envelope. + /// DELETE with a JSON body, returning the decoded data. public async Task DeleteWithBodyAsync(string subPath, QueryParams p, object? body, RequestTier tier, CancellationToken ct) { var url = MergedParams(p).ApplyToUrl(SubUrl(subPath)); @@ -330,9 +307,7 @@ public async Task DeleteWithBodyAsync(string subPath, QueryParams p, return await response.ReadDataObjectAsync(ct).ConfigureAwait(false); } - /// - /// GET returning the raw response body (no data envelope). Returns null on not-found. - /// + /// GET the raw response body; null on 404. public async Task GetRawAsync(string subPath, QueryParams p, RequestTier tier, CancellationToken ct) { var url = MergedParams(p).ApplyToUrl(SubUrl(subPath)); @@ -348,10 +323,8 @@ public async Task DeleteWithBodyAsync(string subPath, QueryParams p, } /// - /// GET returning the raw response body (no data envelope); propagates a 404 instead of swallowing it. - /// Used by sub-path endpoints whose parent resource must already exist for the call to make sense (e.g. - /// dataset statistics, a schedule's log, a task's input), so a missing parent surfaces as an error rather - /// than as an ambiguous empty result. + /// GET the raw response body, propagating a 404. For sub-paths of a resource that must exist, such as dataset + /// statistics or a task's input. /// public async Task GetRawRequiredAsync(string subPath, QueryParams p, RequestTier tier, CancellationToken ct) { @@ -360,10 +333,7 @@ public async Task GetRawRequiredAsync(string subPath, QueryParams p, Req return await response.Content.ReadAsStringAsync(ct).ConfigureAwait(false); } - /// - /// GET returning a JSON body that is not wrapped in a data envelope; propagates a 404 (see - /// for why). Returns null for an empty body. - /// + /// GET a JSON body without a data envelope, propagating a 404; null for an empty body. public async Task GetJsonRequiredAsync(string subPath, QueryParams p, RequestTier tier, CancellationToken ct) { var url = MergedParams(p).ApplyToUrl(SubUrl(subPath)); @@ -371,9 +341,7 @@ public async Task GetRawRequiredAsync(string subPath, QueryParams p, Req return await response.ReadJsonAsync(ct).ConfigureAwait(false); } - /// - /// GET returning a JSON body that is not wrapped in a data envelope. Returns null on not-found. - /// + /// GET a JSON body without a data envelope; null on 404. public async Task GetJsonAsync(string subPath, QueryParams p, RequestTier tier, CancellationToken ct) { try @@ -386,7 +354,7 @@ public async Task GetRawRequiredAsync(string subPath, QueryParams p, Req } } - /// GET returning a data-enveloped object; propagates a 404 (see for why). + /// GET a data object, propagating a 404. public async Task GetDataObjectRequiredAsync(string subPath, QueryParams p, RequestTier tier, CancellationToken ct) { var url = MergedParams(p).ApplyToUrl(SubUrl(subPath)); @@ -394,10 +362,7 @@ public async Task GetDataObjectRequiredAsync(string subPath, QueryPa return await response.ReadDataObjectAsync(ct).ConfigureAwait(false); } - /// - /// GET returning the raw response body as bytes (no data envelope, no UTF-8 decoding — useful when the - /// body is not necessarily text). Returns null on not-found. - /// + /// GET the raw response body as bytes; null on 404. public async Task GetRawBytesAsync(string subPath, QueryParams p, RequestTier tier, CancellationToken ct) { var url = MergedParams(p).ApplyToUrl(SubUrl(subPath)); @@ -412,11 +377,7 @@ public async Task GetDataObjectRequiredAsync(string subPath, QueryPa } } - /// - /// GET returning the raw response body as bytes; propagates a 404 instead of swallowing it. The byte - /// counterpart of (see its remarks on when a missing parent should - /// surface as an error rather than an ambiguous empty result). - /// + /// GET the raw response body as bytes, propagating a 404. public async Task GetRawBytesRequiredAsync(string subPath, QueryParams p, RequestTier tier, CancellationToken ct) { var url = MergedParams(p).ApplyToUrl(SubUrl(subPath)); @@ -440,9 +401,8 @@ public async Task HeadExistsAsync(string subPath, QueryParams p, RequestTi } /// - /// PUT with raw bytes and a content type, with an explicit per-request timeout and retry control. - /// , when given, is sent verbatim as the Content-Encoding - /// header and skips the client's own compression — used for a value that is already compressed. + /// PUT raw bytes with a content type. A is sent as the Content-Encoding + /// header and skips the client's own compression, for an already compressed value. /// public async Task PutRawAsync(string subPath, QueryParams p, byte[] body, string contentType, TimeSpan? timeout, bool doNotRetryTimeouts, RequestTier tier, CancellationToken ct, string? contentEncoding = null) { @@ -450,11 +410,8 @@ public async Task PutRawAsync(string subPath, QueryParams p, byte[] body, string using var response = await Http.CallAsync(HttpMethod.Put, url, null, contentType, timeout ?? _requestTimeout, doNotRetryTimeouts, bodyBytes: body, tier: tier, explicitContentEncoding: contentEncoding, cancellationToken: ct).ConfigureAwait(false); } - // ---- Wait-for-finish ------------------------------------------------------ - /// - /// The largest server-side waitForFinish value that is safe to send: below the configured - /// per-request timeout by a safety margin (or the API's 60s cap when no finite timeout is set). + /// The largest waitForFinish to send: below the request timeout by a margin, or the API's cap when the timeout is not finite. /// private int ServerWaitCapSecs() { @@ -463,9 +420,7 @@ private int ServerWaitCapSecs() } /// - /// Clamps a caller-supplied server-side waitForFinish value (seconds) to the server wait cap, - /// so a synchronous get/wait never asks the server to hold the connection longer than the client's own - /// per-request timeout. Returns null for a null input. + /// Clamps a server-side waitForFinish (seconds) to ; null stays null. /// public int? ClampServerWait(int? waitForFinishSecs) { @@ -478,12 +433,9 @@ private int ServerWaitCapSecs() } /// - /// Polls a GET endpoint with waitForFinish until the resource reaches a terminal state or the - /// wait budget elapses. == null means "wait indefinitely", - /// implemented as a finite but very large bound so the loop always terminates. A 404 is treated as "not - /// yet available" (database replica lag just after creation) for up to - /// of continuous 404s, after which it is reported as a - /// genuine not-found rather than retried for the rest of the (possibly multi-day) wait budget. + /// Polls a resource with waitForFinish until it reaches a terminal state or runs + /// out (null waits for ). A 404 is retried for up to + /// , then reported as a missing resource. /// public async Task WaitForFinishAsync(int? waitSecs, string resourceName, Func isTerminal, CancellationToken ct) { @@ -510,9 +462,7 @@ public async Task WaitForFinishAsync(int? waitSecs, string resourceN var isNotFoundNow = false; try { - // Long tier (the overall per-request budget): the server may hold the connection open for - // up to requestSecs (derived from that same budget via ServerWaitCapSecs), so the HTTP - // timeout for this call must cover the full budget, not a shorter tier. + // Long tier: the server may hold the connection for up to the whole wait budget, so the HTTP timeout must cover it. var data = await GetResourceRequiredAsync(string.Empty, p, RequestTier.Long, ct).ConfigureAwait(false); if (data is JsonObject obj) { @@ -559,12 +509,9 @@ public async Task WaitForFinishAsync(int? waitSecs, string resourceN $"waiting for {resourceName} to finish failed: cannot fetch {resourceName} details from the server"); } - // ---- URL / id helpers ----------------------------------------------------- - /// - /// Turns a resource id into a single URL path segment. Apify uses the username~resourcename form, - /// so every / of an id is replaced with ~; the result is then percent-encoded by - /// , so a hostile id cannot restructure the request path. + /// Turns a resource id into one URL path segment: username/name becomes username~name, then the id is + /// percent-encoded by . /// /// The raw resource id. /// The name of the caller's parameter, reported by the argument exception. @@ -572,10 +519,8 @@ public async Task WaitForFinishAsync(int? waitSecs, string resourceN public static string ToSafeId(string id, string paramName = "id") => EncodePathSegment(id.Replace('/', '~'), paramName); /// - /// Percent-encodes a single URL path segment, so values interpolated into the path (resource ids, record - /// keys, request IDs) cannot break out of the segment. Empty values and the dot segments . and - /// .. are rejected instead of encoded, because URL parsers resolve dot segments after decoding, so - /// %2E%2E would collapse to the parent endpoint just like ... + /// Percent-encodes a URL path segment so a resource id, record key, or request ID cannot break out of it. Empty + /// values and the dot segments . and .. are rejected, because URL parsers resolve %2E%2E like ... /// /// The raw, caller-supplied path segment. /// The name of the caller's parameter, reported by the argument exception. diff --git a/src/Apify.Client/Internal/ResourceLimitRetry.cs b/src/Apify.Client/Internal/ResourceLimitRetry.cs index 585239c..1fe5127 100644 --- a/src/Apify.Client/Internal/ResourceLimitRetry.cs +++ b/src/Apify.Client/Internal/ResourceLimitRetry.cs @@ -7,37 +7,30 @@ namespace Apify.Client.Internal; /// -/// Implements /: -/// retries a run start while the API rejects it for lack of resources. Matches the reference client's +/// Implements and : +/// retries a run start while the API rejects it for lack of resources, as the reference client's /// startWaitingForResources(). /// internal static class ResourceLimitRetry { - /// - /// Error types the API rejects a run start with while the account has no free memory or concurrent-run - /// slot for it. Both clear as other runs or builds finish. - /// + /// Error types of a start rejected because no memory or concurrent-run slot is free; both clear as other runs finish. private const string MemoryLimitErrorType = "actor-memory-limit-exceeded"; private const string ConcurrentRunsLimitErrorType = "concurrent-runs-limit-exceeded"; - /// Cooldown between two attempts to start a run that was rejected for lack of resources. + /// Cooldown between attempts. private const int CooldownMillis = 10_000; /// - /// Calls , retrying it every while it fails - /// with a resource-limit error. null disables retrying - /// entirely (the first error is thrown as-is); otherwise - /// null retries until the call succeeds, and a value bounds the retrying, after which the last - /// error is thrown. Any other error is thrown right away. + /// Calls and retries it every while it fails with a + /// resource-limit error. A null throws the first error; + /// null retries until success, and a value bounds the retrying before + /// the last error is thrown. Other errors are thrown at once. /// /// Makes one start attempt. /// The retry configuration, or null to disable retrying. /// A token to cancel the wait between attempts. - /// - /// The cooldown between attempts; defaults to . Overridable only so unit - /// tests can exercise multi-attempt retries without a real 10-second wait per attempt. - /// + /// The cooldown between attempts; a parameter so unit tests need not wait 10 seconds. public static async Task StartAsync( Func> start, WaitForResources? waitForResources, diff --git a/src/Apify.Client/Internal/ResponseOwningStream.cs b/src/Apify.Client/Internal/ResponseOwningStream.cs index b5b16a4..e667aac 100644 --- a/src/Apify.Client/Internal/ResponseOwningStream.cs +++ b/src/Apify.Client/Internal/ResponseOwningStream.cs @@ -7,9 +7,8 @@ namespace Apify.Client.Internal; /// -/// A read-only stream over an HTTP response body that also owns the , -/// disposing it (and thus releasing the connection) when the stream is disposed. Used for live log -/// streaming, where the response must stay open while the caller reads the body incrementally. +/// A read-only stream over a response body that owns the and disposes it, releasing +/// the connection, with the stream. For live log streaming. /// internal sealed class ResponseOwningStream : Stream { diff --git a/src/Apify.Client/Internal/ResponseReader.cs b/src/Apify.Client/Internal/ResponseReader.cs index ef6d0c1..d5870c5 100644 --- a/src/Apify.Client/Internal/ResponseReader.cs +++ b/src/Apify.Client/Internal/ResponseReader.cs @@ -9,11 +9,9 @@ namespace Apify.Client.Internal; /// -/// Reads a successful JSON response into a and reports a body that is unparsable -/// () or does not match the schema the API specification -/// declares for the operation (, see ), -/// with the request's method and path as context. Every successful JSON response is decoded through here, -/// matching the reference client's parseResponse. +/// Reads a successful JSON response and reports a body that is not JSON () or +/// does not match the schema of its operation (, see +/// ), with the request's method and path. As the reference client's parseResponse. /// internal static class ResponseReader { @@ -44,10 +42,7 @@ public static async Task ReadDataObjectAsync(this HttpResponseMessag return UnwrapData(response, body) as JsonObject ?? throw Invalid(response, body, "the response's `data` is not an object"); } - /// - /// Parses an enveloped body, validates it against the specification's schema for the operation, and - /// returns the unwrapped non-null data. - /// + /// Parses an enveloped body, validates it against the operation's schema and returns its non-null data. private static JsonNode UnwrapData(HttpResponseMessage response, string body) { var node = Parse(response, body); diff --git a/src/Apify.Client/Internal/ResponseSchemas.cs b/src/Apify.Client/Internal/ResponseSchemas.cs index 5f43e24..c2cec7e 100644 --- a/src/Apify.Client/Internal/ResponseSchemas.cs +++ b/src/Apify.Client/Internal/ResponseSchemas.cs @@ -12,17 +12,15 @@ namespace Apify.Client.Internal; /// -/// Validates a successful JSON response against the schema the Apify OpenAPI specification declares for -/// its operation, the way the reference client's parseResponse does with its generated schemas. +/// Validates a successful JSON response against the schema the OpenAPI specification declares for its operation, as the +/// reference client's parseResponse does. /// /// -/// The schemas come from the embedded ResponseSchemas.json, generated from the specification by -/// tools/generate_response_schemas.py. Validation covers JSON types, required properties (including -/// nullability), nested properties and array items, the allOf/anyOf/oneOf combinators, -/// and value constraints: the date-time and uri formats, pattern, -/// minimum/maximum and minLength/maxLength. Like the reference client's -/// schemas, it lets unknown properties and unknown enum values through, so an API that grows new fields or -/// values does not break existing clients. A request that matches no documented operation is not validated. +/// The schemas are in the embedded ResponseSchemas.json, generated by tools/generate_response_schemas.py. +/// Validation covers JSON types, required properties and nullability, nested properties and items, +/// allOf/anyOf/oneOf, the date-time and uri formats, pattern, bounds and +/// lengths. Unknown properties and enum values pass, so new API fields do not break existing clients. A request that +/// matches no documented operation is not validated. /// internal static class ResponseSchemas { @@ -49,9 +47,8 @@ internal static class ResponseSchemas public static string SpecVersion => Loaded.Value.SpecVersion; /// - /// Validates against the schema documented for the operation identified by - /// and (the request path, optionally with a query). - /// Returns the problems found, empty when the body is valid or the operation is not documented. + /// Validates against the schema of the operation identified by and + /// . Returns the problems found; empty when the body is valid or the operation is undocumented. /// public static IReadOnlyList Validate(string method, string path, JsonNode? body) { diff --git a/src/Apify.Client/Internal/ResponseSchemas.json b/src/Apify.Client/Internal/ResponseSchemas.json index ab88daf..8eafc10 100644 --- a/src/Apify.Client/Internal/ResponseSchemas.json +++ b/src/Apify.Client/Internal/ResponseSchemas.json @@ -1 +1 @@ -{"operations":{"DELETE /v2/actor-runs/{runId}/request-queue/requests/batch":{"$ref":"#/components/schemas/BatchDeleteResponse"},"DELETE /v2/actor-tasks/{actorTaskId}":{"type":"object"},"DELETE /v2/actor-tasks/{actorTaskId}/runs/last/request-queue/requests/batch":{"$ref":"#/components/schemas/BatchDeleteResponse"},"DELETE /v2/actors/{actorId}":{"type":"object"},"DELETE /v2/actors/{actorId}/runs/last/request-queue/requests/batch":{"$ref":"#/components/schemas/BatchDeleteResponse"},"DELETE /v2/actors/{actorId}/versions/{versionNumber}":{"type":"object"},"DELETE /v2/actors/{actorId}/versions/{versionNumber}/env-vars/{envVarName}":{"type":"object"},"DELETE /v2/browser-info":{"$ref":"#/components/schemas/BrowserInfoResponse"},"DELETE /v2/request-queues/{queueId}/requests/batch":{"$ref":"#/components/schemas/BatchDeleteResponse"},"DELETE /v2/schedules/{scheduleId}":{"type":"object"},"DELETE /v2/webhooks/{webhookId}":{"type":"object"},"GET /v2/actor-builds":{"$ref":"#/components/schemas/ListOfBuildsResponse"},"GET /v2/actor-builds/{buildId}":{"allOf":[{"$ref":"#/components/schemas/BuildResponse"}]},"GET /v2/actor-builds/{buildId}/openapi.json":{"type":"object"},"GET /v2/actor-oauth-accounts/{accountId}":{"$ref":"#/components/schemas/ActorOAuthAccountResponse"},"GET /v2/actor-oauth-accounts/{connectionId}/accounts":{"$ref":"#/components/schemas/ListOfActorOAuthAccountsResponse"},"GET /v2/actor-runs":{"allOf":[{"$ref":"#/components/schemas/ListOfRunsResponse"}]},"GET /v2/actor-runs/{runId}":{"allOf":[{"$ref":"#/components/schemas/RunResponse"}]},"GET /v2/actor-runs/{runId}/dataset":{"$ref":"#/components/schemas/DatasetResponse"},"GET /v2/actor-runs/{runId}/dataset/items":{"items":{"type":"object"},"type":"array"},"GET /v2/actor-runs/{runId}/dataset/statistics":{"$ref":"#/components/schemas/DatasetStatisticsResponse"},"GET /v2/actor-runs/{runId}/key-value-store":{"$ref":"#/components/schemas/KeyValueStoreResponse"},"GET /v2/actor-runs/{runId}/key-value-store/keys":{"$ref":"#/components/schemas/ListOfKeysResponse"},"GET /v2/actor-runs/{runId}/key-value-store/records/{recordKey}":{"$ref":"#/components/schemas/RecordResponse"},"GET /v2/actor-runs/{runId}/request-queue":{"$ref":"#/components/schemas/RequestQueueResponse"},"GET /v2/actor-runs/{runId}/request-queue/head":{"$ref":"#/components/schemas/RequestQueueHeadResponse"},"GET /v2/actor-runs/{runId}/request-queue/requests":{"$ref":"#/components/schemas/ListOfRequestsResponse"},"GET /v2/actor-runs/{runId}/request-queue/requests/{requestId}":{"$ref":"#/components/schemas/RequestResponse"},"GET /v2/actor-tasks":{"$ref":"#/components/schemas/ListOfTasksResponse"},"GET /v2/actor-tasks/{actorTaskId}":{"properties":{"data":{"$ref":"#/components/schemas/Task"}},"required":["data"],"type":"object"},"GET /v2/actor-tasks/{actorTaskId}/input":{"type":"object"},"GET /v2/actor-tasks/{actorTaskId}/run-sync":{"type":"object"},"GET /v2/actor-tasks/{actorTaskId}/run-sync-get-dataset-items":{"type":"object"},"GET /v2/actor-tasks/{actorTaskId}/runs":{"properties":{"data":{"allOf":[{"$ref":"#/components/schemas/PaginationResponse"},{"properties":{"items":{"items":{"$ref":"#/components/schemas/RunListItem"},"type":"array"}},"required":["items"],"type":"object"}]}},"required":["data"],"type":"object"},"GET /v2/actor-tasks/{actorTaskId}/runs/last":{"properties":{"data":{"$ref":"#/components/schemas/Run"}},"required":["data"],"type":"object"},"GET /v2/actor-tasks/{actorTaskId}/runs/last/dataset":{"$ref":"#/components/schemas/DatasetResponse"},"GET /v2/actor-tasks/{actorTaskId}/runs/last/dataset/items":{"items":{"type":"object"},"type":"array"},"GET /v2/actor-tasks/{actorTaskId}/runs/last/dataset/statistics":{"$ref":"#/components/schemas/DatasetStatisticsResponse"},"GET /v2/actor-tasks/{actorTaskId}/runs/last/key-value-store":{"$ref":"#/components/schemas/KeyValueStoreResponse"},"GET /v2/actor-tasks/{actorTaskId}/runs/last/key-value-store/keys":{"$ref":"#/components/schemas/ListOfKeysResponse"},"GET /v2/actor-tasks/{actorTaskId}/runs/last/key-value-store/records/{recordKey}":{"$ref":"#/components/schemas/RecordResponse"},"GET /v2/actor-tasks/{actorTaskId}/runs/last/request-queue":{"$ref":"#/components/schemas/RequestQueueResponse"},"GET /v2/actor-tasks/{actorTaskId}/runs/last/request-queue/head":{"$ref":"#/components/schemas/RequestQueueHeadResponse"},"GET /v2/actor-tasks/{actorTaskId}/runs/last/request-queue/requests":{"$ref":"#/components/schemas/ListOfRequestsResponse"},"GET /v2/actor-tasks/{actorTaskId}/runs/last/request-queue/requests/{requestId}":{"$ref":"#/components/schemas/RequestResponse"},"GET /v2/actor-tasks/{actorTaskId}/webhooks":{"properties":{"data":{"allOf":[{"$ref":"#/components/schemas/PaginationResponse"},{"properties":{"items":{"items":{"$ref":"#/components/schemas/WebhookResource"},"type":"array"}},"required":["items"],"type":"object"}]}},"required":["data"],"type":"object"},"GET /v2/actors":{"$ref":"#/components/schemas/ListOfActorsResponse"},"GET /v2/actors/{actorId}":{"$ref":"#/components/schemas/ActorResponse"},"GET /v2/actors/{actorId}/builds":{"$ref":"#/components/schemas/ListOfBuildsResponse"},"GET /v2/actors/{actorId}/builds/default":{"$ref":"#/components/schemas/BuildResponse"},"GET /v2/actors/{actorId}/builds/{buildId}":{"$ref":"#/components/schemas/BuildResponse"},"GET /v2/actors/{actorId}/builds/{buildId}/openapi.json":{"type":"object"},"GET /v2/actors/{actorId}/oauth-connections":{"$ref":"#/components/schemas/ListOfActorOAuthConnectionsResponse"},"GET /v2/actors/{actorId}/run-sync":{"type":"object"},"GET /v2/actors/{actorId}/run-sync-get-dataset-items":{"items":{"type":"object"},"type":"array"},"GET /v2/actors/{actorId}/runs":{"$ref":"#/components/schemas/ListOfRunsResponse"},"GET /v2/actors/{actorId}/runs/last":{"$ref":"#/components/schemas/RunResponse"},"GET /v2/actors/{actorId}/runs/last/dataset":{"$ref":"#/components/schemas/DatasetResponse"},"GET /v2/actors/{actorId}/runs/last/dataset/items":{"items":{"type":"object"},"type":"array"},"GET /v2/actors/{actorId}/runs/last/dataset/statistics":{"$ref":"#/components/schemas/DatasetStatisticsResponse"},"GET /v2/actors/{actorId}/runs/last/key-value-store":{"$ref":"#/components/schemas/KeyValueStoreResponse"},"GET /v2/actors/{actorId}/runs/last/key-value-store/keys":{"$ref":"#/components/schemas/ListOfKeysResponse"},"GET /v2/actors/{actorId}/runs/last/key-value-store/records/{recordKey}":{"$ref":"#/components/schemas/RecordResponse"},"GET /v2/actors/{actorId}/runs/last/request-queue":{"$ref":"#/components/schemas/RequestQueueResponse"},"GET /v2/actors/{actorId}/runs/last/request-queue/head":{"$ref":"#/components/schemas/RequestQueueHeadResponse"},"GET /v2/actors/{actorId}/runs/last/request-queue/requests":{"$ref":"#/components/schemas/ListOfRequestsResponse"},"GET /v2/actors/{actorId}/runs/last/request-queue/requests/{requestId}":{"$ref":"#/components/schemas/RequestResponse"},"GET /v2/actors/{actorId}/runs/{runId}":{"$ref":"#/components/schemas/RunResponse"},"GET /v2/actors/{actorId}/versions":{"$ref":"#/components/schemas/ListOfVersionsResponse"},"GET /v2/actors/{actorId}/versions/{versionNumber}":{"$ref":"#/components/schemas/VersionResponse"},"GET /v2/actors/{actorId}/versions/{versionNumber}/env-vars":{"$ref":"#/components/schemas/ListOfEnvVarsResponse"},"GET /v2/actors/{actorId}/versions/{versionNumber}/env-vars/{envVarName}":{"$ref":"#/components/schemas/EnvVarResponse"},"GET /v2/actors/{actorId}/webhooks":{"$ref":"#/components/schemas/ListOfWebhookResourcesResponse"},"GET /v2/browser-info":{"$ref":"#/components/schemas/BrowserInfoResponse"},"GET /v2/datasets":{"$ref":"#/components/schemas/ListOfDatasetsResponse"},"GET /v2/datasets/{datasetId}":{"$ref":"#/components/schemas/DatasetResponse"},"GET /v2/datasets/{datasetId}/items":{"items":{"type":"object"},"type":"array"},"GET /v2/datasets/{datasetId}/statistics":{"$ref":"#/components/schemas/DatasetStatisticsResponse"},"GET /v2/integrations/git":{"$ref":"#/components/schemas/ListOfGitProviderIntegrationsResponse"},"GET /v2/key-value-stores":{"$ref":"#/components/schemas/ListOfKeyValueStoresResponse"},"GET /v2/key-value-stores/{storeId}":{"$ref":"#/components/schemas/KeyValueStoreResponse"},"GET /v2/key-value-stores/{storeId}/keys":{"$ref":"#/components/schemas/ListOfKeysResponse"},"GET /v2/key-value-stores/{storeId}/records/{recordKey}":{"$ref":"#/components/schemas/RecordResponse"},"GET /v2/request-queues":{"$ref":"#/components/schemas/ListOfRequestQueuesResponse"},"GET /v2/request-queues/{queueId}":{"$ref":"#/components/schemas/RequestQueueResponse"},"GET /v2/request-queues/{queueId}/head":{"$ref":"#/components/schemas/RequestQueueHeadResponse"},"GET /v2/request-queues/{queueId}/requests":{"$ref":"#/components/schemas/ListOfRequestsResponse"},"GET /v2/request-queues/{queueId}/requests/{requestId}":{"$ref":"#/components/schemas/RequestResponse"},"GET /v2/schedules":{"$ref":"#/components/schemas/ListOfSchedulesResponse"},"GET /v2/schedules/{scheduleId}":{"$ref":"#/components/schemas/ScheduleResponse"},"GET /v2/schedules/{scheduleId}/log":{"$ref":"#/components/schemas/ScheduleLogResponse"},"GET /v2/store":{"$ref":"#/components/schemas/ListOfStoreActorsResponse"},"GET /v2/users/me":{"$ref":"#/components/schemas/UserPrivateInfoResponse"},"GET /v2/users/me/limits":{"$ref":"#/components/schemas/AccountLimitsResponse"},"GET /v2/users/me/usage/monthly":{"$ref":"#/components/schemas/MonthlyUsageResponse"},"GET /v2/users/{userId}":{"anyOf":[{"$ref":"#/components/schemas/UserPublicInfoResponse"},{"$ref":"#/components/schemas/UserPrivateInfoResponse"}]},"GET /v2/users/{userId}/limits":{"$ref":"#/components/schemas/AccountLimitsResponse"},"GET /v2/users/{userId}/usage/monthly":{"$ref":"#/components/schemas/MonthlyUsageResponse"},"GET /v2/webhook-dispatches":{"$ref":"#/components/schemas/ListOfWebhookDispatchesResponse"},"GET /v2/webhook-dispatches/{dispatchId}":{"$ref":"#/components/schemas/WebhookDispatchResponse"},"GET /v2/webhooks":{"$ref":"#/components/schemas/ListOfWebhooksResponse"},"GET /v2/webhooks/{webhookId}":{"$ref":"#/components/schemas/WebhookResponse"},"GET /v2/webhooks/{webhookId}/dispatches":{"$ref":"#/components/schemas/ListOfWebhookDispatchesResponse"},"POST /v2/actor-builds/{buildId}/abort":{"allOf":[{"$ref":"#/components/schemas/BuildResponse"}]},"POST /v2/actor-runs/{runId}/abort":{"$ref":"#/components/schemas/RunResponse"},"POST /v2/actor-runs/{runId}/charge":{"type":"object"},"POST /v2/actor-runs/{runId}/dataset/items":{"type":"object"},"POST /v2/actor-runs/{runId}/key-value-store/records/{recordKey}":{"type":"object"},"POST /v2/actor-runs/{runId}/metamorph":{"$ref":"#/components/schemas/RunResponse"},"POST /v2/actor-runs/{runId}/reboot":{"$ref":"#/components/schemas/RunResponse"},"POST /v2/actor-runs/{runId}/request-queue/head/lock":{"$ref":"#/components/schemas/LockedRequestQueueHeadResponse"},"POST /v2/actor-runs/{runId}/request-queue/requests":{"$ref":"#/components/schemas/AddRequestResponse"},"POST /v2/actor-runs/{runId}/request-queue/requests/batch":{"$ref":"#/components/schemas/BatchAddResponse"},"POST /v2/actor-runs/{runId}/request-queue/requests/unlock":{"$ref":"#/components/schemas/UnlockRequestsResponse"},"POST /v2/actor-runs/{runId}/resurrect":{"allOf":[{"$ref":"#/components/schemas/RunResponse"}]},"POST /v2/actor-tasks":{"$ref":"#/components/schemas/TaskResponse"},"POST /v2/actor-tasks/{actorTaskId}/run-sync":{"type":"object"},"POST /v2/actor-tasks/{actorTaskId}/run-sync-get-dataset-items":{"type":"object"},"POST /v2/actor-tasks/{actorTaskId}/runs":{"properties":{"data":{"$ref":"#/components/schemas/Run"}},"required":["data"],"type":"object"},"POST /v2/actor-tasks/{actorTaskId}/runs/last/abort":{"$ref":"#/components/schemas/RunResponse"},"POST /v2/actor-tasks/{actorTaskId}/runs/last/dataset/items":{"type":"object"},"POST /v2/actor-tasks/{actorTaskId}/runs/last/key-value-store/records/{recordKey}":{"type":"object"},"POST /v2/actor-tasks/{actorTaskId}/runs/last/metamorph":{"$ref":"#/components/schemas/RunResponse"},"POST /v2/actor-tasks/{actorTaskId}/runs/last/reboot":{"$ref":"#/components/schemas/RunResponse"},"POST /v2/actor-tasks/{actorTaskId}/runs/last/request-queue/head/lock":{"$ref":"#/components/schemas/LockedRequestQueueHeadResponse"},"POST /v2/actor-tasks/{actorTaskId}/runs/last/request-queue/requests":{"$ref":"#/components/schemas/AddRequestResponse"},"POST /v2/actor-tasks/{actorTaskId}/runs/last/request-queue/requests/batch":{"$ref":"#/components/schemas/BatchAddResponse"},"POST /v2/actor-tasks/{actorTaskId}/runs/last/request-queue/requests/unlock":{"$ref":"#/components/schemas/UnlockRequestsResponse"},"POST /v2/actors":{"$ref":"#/components/schemas/ActorResponse"},"POST /v2/actors/{actorId}/builds":{"$ref":"#/components/schemas/BuildResponse"},"POST /v2/actors/{actorId}/builds/{buildId}/abort":{"$ref":"#/components/schemas/BuildResponse"},"POST /v2/actors/{actorId}/run-sync":{"type":"object"},"POST /v2/actors/{actorId}/run-sync-get-dataset-items":{"items":{"type":"object"},"type":"array"},"POST /v2/actors/{actorId}/runs":{"$ref":"#/components/schemas/RunResponse"},"POST /v2/actors/{actorId}/runs/last/abort":{"$ref":"#/components/schemas/RunResponse"},"POST /v2/actors/{actorId}/runs/last/dataset/items":{"type":"object"},"POST /v2/actors/{actorId}/runs/last/key-value-store/records/{recordKey}":{"type":"object"},"POST /v2/actors/{actorId}/runs/last/metamorph":{"$ref":"#/components/schemas/RunResponse"},"POST /v2/actors/{actorId}/runs/last/reboot":{"$ref":"#/components/schemas/RunResponse"},"POST /v2/actors/{actorId}/runs/last/request-queue/head/lock":{"$ref":"#/components/schemas/LockedRequestQueueHeadResponse"},"POST /v2/actors/{actorId}/runs/last/request-queue/requests":{"$ref":"#/components/schemas/AddRequestResponse"},"POST /v2/actors/{actorId}/runs/last/request-queue/requests/batch":{"$ref":"#/components/schemas/BatchAddResponse"},"POST /v2/actors/{actorId}/runs/last/request-queue/requests/unlock":{"$ref":"#/components/schemas/UnlockRequestsResponse"},"POST /v2/actors/{actorId}/runs/{runId}/abort":{"$ref":"#/components/schemas/RunResponse"},"POST /v2/actors/{actorId}/runs/{runId}/metamorph":{"$ref":"#/components/schemas/RunResponse"},"POST /v2/actors/{actorId}/runs/{runId}/resurrect":{"allOf":[{"$ref":"#/components/schemas/RunResponse"}]},"POST /v2/actors/{actorId}/validate-input":{"properties":{"valid":{"type":"boolean"}},"required":["valid"],"type":"object"},"POST /v2/actors/{actorId}/versions":{"$ref":"#/components/schemas/VersionResponse"},"POST /v2/actors/{actorId}/versions/{versionNumber}":{"$ref":"#/components/schemas/VersionResponse"},"POST /v2/actors/{actorId}/versions/{versionNumber}/env-vars":{"$ref":"#/components/schemas/EnvVarResponse"},"POST /v2/actors/{actorId}/versions/{versionNumber}/env-vars/{envVarName}":{"$ref":"#/components/schemas/EnvVarResponse"},"POST /v2/browser-info":{"$ref":"#/components/schemas/BrowserInfoResponse"},"POST /v2/datasets":{"$ref":"#/components/schemas/DatasetResponse"},"POST /v2/datasets/{datasetId}/items":{"type":"object"},"POST /v2/integrations/git/{providerId}/create-repo":{"$ref":"#/components/schemas/GitRepoResponse"},"POST /v2/integrations/git/{providerId}/register-deployment-key":{"$ref":"#/components/schemas/RegisterDeploymentKeyResponse"},"POST /v2/key-value-stores":{"$ref":"#/components/schemas/KeyValueStoreResponse"},"POST /v2/key-value-stores/{storeId}/records/{recordKey}":{"type":"object"},"POST /v2/request-queues":{"$ref":"#/components/schemas/RequestQueueResponse"},"POST /v2/request-queues/{queueId}/head/lock":{"$ref":"#/components/schemas/LockedRequestQueueHeadResponse"},"POST /v2/request-queues/{queueId}/requests":{"$ref":"#/components/schemas/AddRequestResponse"},"POST /v2/request-queues/{queueId}/requests/batch":{"$ref":"#/components/schemas/BatchAddResponse"},"POST /v2/request-queues/{queueId}/requests/unlock":{"$ref":"#/components/schemas/UnlockRequestsResponse"},"POST /v2/schedules":{"$ref":"#/components/schemas/ScheduleResponse"},"POST /v2/schedules/{scheduleId}/invoke":{"type":"object"},"POST /v2/tools/decode-and-verify":{"$ref":"#/components/schemas/DecodeAndVerifyResponse"},"POST /v2/tools/encode-and-sign":{"$ref":"#/components/schemas/EncodeAndSignResponse"},"POST /v2/webhooks":{"$ref":"#/components/schemas/WebhookResponse"},"POST /v2/webhooks/{webhookId}/test":{"$ref":"#/components/schemas/WebhookDispatchResponse"},"PUT /v2/actor-runs/{runId}":{"$ref":"#/components/schemas/RunResponse"},"PUT /v2/actor-runs/{runId}/dataset":{"$ref":"#/components/schemas/DatasetResponse"},"PUT /v2/actor-runs/{runId}/key-value-store":{"$ref":"#/components/schemas/KeyValueStoreResponse"},"PUT /v2/actor-runs/{runId}/key-value-store/records/{recordKey}":{"type":"object"},"PUT /v2/actor-runs/{runId}/request-queue":{"$ref":"#/components/schemas/RequestQueueResponse"},"PUT /v2/actor-runs/{runId}/request-queue/requests/{requestId}":{"$ref":"#/components/schemas/UpdateRequestResponse"},"PUT /v2/actor-runs/{runId}/request-queue/requests/{requestId}/lock":{"$ref":"#/components/schemas/ProlongRequestLockResponse"},"PUT /v2/actor-tasks/{actorTaskId}":{"properties":{"data":{"$ref":"#/components/schemas/Task"}},"required":["data"],"type":"object"},"PUT /v2/actor-tasks/{actorTaskId}/input":{"type":"object"},"PUT /v2/actor-tasks/{actorTaskId}/runs/last/dataset":{"$ref":"#/components/schemas/DatasetResponse"},"PUT /v2/actor-tasks/{actorTaskId}/runs/last/key-value-store":{"$ref":"#/components/schemas/KeyValueStoreResponse"},"PUT /v2/actor-tasks/{actorTaskId}/runs/last/key-value-store/records/{recordKey}":{"type":"object"},"PUT /v2/actor-tasks/{actorTaskId}/runs/last/request-queue":{"$ref":"#/components/schemas/RequestQueueResponse"},"PUT /v2/actor-tasks/{actorTaskId}/runs/last/request-queue/requests/{requestId}":{"$ref":"#/components/schemas/UpdateRequestResponse"},"PUT /v2/actor-tasks/{actorTaskId}/runs/last/request-queue/requests/{requestId}/lock":{"$ref":"#/components/schemas/ProlongRequestLockResponse"},"PUT /v2/actors/{actorId}":{"$ref":"#/components/schemas/ActorResponse"},"PUT /v2/actors/{actorId}/runs/last/dataset":{"$ref":"#/components/schemas/DatasetResponse"},"PUT /v2/actors/{actorId}/runs/last/key-value-store":{"$ref":"#/components/schemas/KeyValueStoreResponse"},"PUT /v2/actors/{actorId}/runs/last/key-value-store/records/{recordKey}":{"type":"object"},"PUT /v2/actors/{actorId}/runs/last/request-queue":{"$ref":"#/components/schemas/RequestQueueResponse"},"PUT /v2/actors/{actorId}/runs/last/request-queue/requests/{requestId}":{"$ref":"#/components/schemas/UpdateRequestResponse"},"PUT /v2/actors/{actorId}/runs/last/request-queue/requests/{requestId}/lock":{"$ref":"#/components/schemas/ProlongRequestLockResponse"},"PUT /v2/actors/{actorId}/versions/{versionNumber}":{"$ref":"#/components/schemas/VersionResponse"},"PUT /v2/actors/{actorId}/versions/{versionNumber}/env-vars/{envVarName}":{"$ref":"#/components/schemas/EnvVarResponse"},"PUT /v2/browser-info":{"$ref":"#/components/schemas/BrowserInfoResponse"},"PUT /v2/datasets/{datasetId}":{"$ref":"#/components/schemas/DatasetResponse"},"PUT /v2/key-value-stores/{storeId}":{"$ref":"#/components/schemas/KeyValueStoreResponse"},"PUT /v2/key-value-stores/{storeId}/records/{recordKey}":{"type":"object"},"PUT /v2/request-queues/{queueId}":{"$ref":"#/components/schemas/RequestQueueResponse"},"PUT /v2/request-queues/{queueId}/requests/{requestId}":{"$ref":"#/components/schemas/UpdateRequestResponse"},"PUT /v2/request-queues/{queueId}/requests/{requestId}/lock":{"$ref":"#/components/schemas/ProlongRequestLockResponse"},"PUT /v2/schedules/{scheduleId}":{"$ref":"#/components/schemas/ScheduleResponse"},"PUT /v2/users/me/limits":{"type":"object"},"PUT /v2/users/{userId}/limits":{"type":"object"},"PUT /v2/webhooks/{webhookId}":{"$ref":"#/components/schemas/WebhookResponse"}},"schemas":{"AccountLimits":{"properties":{"current":{"$ref":"#/components/schemas/CurrentUsage"},"limits":{"$ref":"#/components/schemas/Limits"},"monthlyUsageCycle":{"$ref":"#/components/schemas/UsageCycle"}},"required":["monthlyUsageCycle","limits","current"],"type":"object"},"AccountLimitsResponse":{"properties":{"data":{"$ref":"#/components/schemas/AccountLimits"}},"required":["data"],"type":"object"},"ActorDefinition":{"properties":{"actorSpecification":{"type":"integer"},"buildTag":{"type":"string"},"changelog":{"type":["string","null"]},"defaultMemoryMbytes":{"oneOf":[{"type":"string"},{"type":"integer"}]},"description":{"type":"string"},"dockerContextDir":{"type":"string"},"dockerfile":{"type":"string"},"environmentVariables":{"type":"object"},"input":{"type":["object","null"]},"maxMemoryMbytes":{"minimum":128,"type":"integer"},"meta":{"$ref":"#/components/schemas/ActorDefinitionMeta"},"minMemoryMbytes":{"minimum":128,"type":"integer"},"name":{"type":"string"},"output":{"type":["object","null"]},"readme":{"type":["string","null"]},"storages":{"properties":{"dataset":{"type":"object"},"datasets":{"type":"object"},"keyValueStore":{"type":"object"}},"type":"object"},"title":{"type":"string"},"usesStandbyMode":{"type":"boolean"},"version":{"pattern":"^[0-9]+(\\.[0-9]+)+$","type":"string"},"webServerMcpPath":{"type":"string"},"webServerSchema":{"type":"object"}},"type":"object"},"ActorDefinitionMeta":{"properties":{"generatedBy":{"type":"string"},"templateId":{"type":"string"}},"type":"object"},"ActorJobStatus":{"type":"string"},"ActorListItem":{"properties":{"createdAt":{"format":"date-time","type":"string"},"id":{"type":"string"},"modifiedAt":{"format":"date-time","type":"string"},"name":{"type":"string"},"stats":{"anyOf":[{"$ref":"#/components/schemas/ActorStats"},{"type":"null"}]},"title":{"type":"string"},"username":{"type":"string"}},"required":["id","createdAt","modifiedAt","name","username"],"type":"object"},"ActorNotice":{"type":["string","null"]},"ActorOAuthAccount":{"properties":{"_id":{"type":"string"},"authorizedAt":{"format":"date-time","type":["string","null"]},"connectionId":{"type":"string"},"createdAt":{"format":"date-time","type":"string"},"data":{"type":"object"},"description":{"type":["string","null"]},"modifiedAt":{"format":"date-time","type":"string"},"userId":{"type":"string"}},"required":["_id","userId","connectionId","createdAt","modifiedAt","data"],"type":"object"},"ActorOAuthAccountResponse":{"properties":{"data":{"$ref":"#/components/schemas/ActorOAuthAccount"}},"required":["data"],"type":"object"},"ActorOAuthConnection":{"properties":{"_id":{"type":"string"},"actorId":{"type":"string"},"createdAt":{"format":"date-time","type":"string"},"credentials":{"properties":{"clientId":{"type":"string"},"imageUrl":{"type":"string"},"label":{"type":"string"},"scope":{"type":"string"}},"type":"object"},"modifiedAt":{"format":"date-time","type":"string"},"oAuthProviderType":{"type":"string"}},"required":["_id","actorId","oAuthProviderType","createdAt","modifiedAt"],"type":"object"},"ActorPermissionLevel":{"type":"string"},"ActorResource":{"properties":{"actorPermissionLevel":{"$ref":"#/components/schemas/ActorPermissionLevel"},"actorStandby":{"anyOf":[{"$ref":"#/components/schemas/ActorStandby"},{"type":"null"}]},"categories":{"items":{"type":"string"},"type":"array"},"createdAt":{"format":"date-time","type":"string"},"defaultRunOptions":{"$ref":"#/components/schemas/DefaultRunOptions"},"deploymentKey":{"type":"string"},"description":{"type":["string","null"]},"exampleRunInput":{"anyOf":[{"$ref":"#/components/schemas/ExampleRunInput"},{"type":"null"}]},"hasNoDataset":{"type":"boolean"},"id":{"type":"string"},"isCritical":{"type":"boolean"},"isDeprecated":{"type":["boolean","null"]},"isGeneric":{"type":"boolean"},"isPublic":{"type":"boolean"},"isSourceCodeHidden":{"type":"boolean"},"modifiedAt":{"format":"date-time","type":"string"},"name":{"type":"string"},"notice":{"$ref":"#/components/schemas/ActorNotice"},"pictureUrl":{"type":["string","null"]},"pricingInfos":{"items":{"$ref":"#/components/schemas/ActorRunPricingInfo"},"type":"array"},"readmeSummary":{"type":"string"},"restartOnError":{"type":["boolean","null"]},"seoDescription":{"type":["string","null"]},"seoTitle":{"type":["string","null"]},"standbyUrl":{"type":["string","null"]},"stats":{"$ref":"#/components/schemas/ActorStats"},"taggedBuilds":{"anyOf":[{"$ref":"#/components/schemas/TaggedBuilds"},{"type":"null"}]},"title":{"type":["string","null"]},"userId":{"type":"string"},"username":{"type":"string"},"versions":{"items":{"$ref":"#/components/schemas/Version"},"type":"array"}},"required":["id","userId","name","username","isPublic","createdAt","modifiedAt","stats","versions","defaultRunOptions"],"type":"object"},"ActorResponse":{"properties":{"data":{"$ref":"#/components/schemas/ActorResource"}},"required":["data"],"type":"object"},"ActorRunPricingInfo":{"oneOf":[{"$ref":"#/components/schemas/PayPerEventActorPricingInfo"},{"$ref":"#/components/schemas/PricePerDatasetItemActorPricingInfo"},{"$ref":"#/components/schemas/FlatPricePerMonthActorPricingInfo"},{"$ref":"#/components/schemas/FreeActorPricingInfo"}]},"ActorStandby":{"properties":{"build":{"type":["string","null"]},"desiredRequestsPerActorRun":{"type":["integer","null"]},"disableStandbyFieldsOverride":{"type":["boolean","null"]},"idleTimeoutSecs":{"type":["integer","null"]},"isConsoleAuthEnabled":{"type":["boolean","null"]},"isEnabled":{"type":["boolean","null"]},"isTokenlessEnabled":{"type":["boolean","null"]},"maxRequestsPerActorRun":{"type":["integer","null"]},"memoryMbytes":{"type":["integer","null"]},"shouldPassActorInput":{"type":["boolean","null"]},"tenancy":{"type":["string","null"]}},"type":"object"},"ActorStats":{"properties":{"actorReviewCount":{"type":"integer"},"actorReviewRating":{"type":"number"},"bookmarkCount":{"type":"integer"},"lastRunStartedAt":{"format":"date-time","type":"string"},"publicActorRunStats30Days":{"properties":{"ABORTED":{"type":"integer"},"FAILED":{"type":"integer"},"SUCCEEDED":{"type":"integer"},"TIMED-OUT":{"type":"integer"},"TOTAL":{"type":"integer"}},"type":"object"},"totalBuilds":{"type":"integer"},"totalMetamorphs":{"type":"integer"},"totalRuns":{"type":"integer"},"totalUsers":{"type":"integer"},"totalUsers30Days":{"type":"integer"},"totalUsers7Days":{"type":"integer"},"totalUsers90Days":{"type":"integer"}},"type":"object"},"AddRequestResponse":{"properties":{"data":{"$ref":"#/components/schemas/RequestRegistration"}},"required":["data"],"type":"object"},"AddedRequest":{"properties":{"requestId":{"$ref":"#/components/schemas/RequestId"},"uniqueKey":{"$ref":"#/components/schemas/UniqueKey"},"wasAlreadyHandled":{"$ref":"#/components/schemas/WasAlreadyHandled"},"wasAlreadyPresent":{"$ref":"#/components/schemas/WasAlreadyPresent"}},"required":["requestId","uniqueKey","wasAlreadyHandled","wasAlreadyPresent"],"type":"object"},"AvailableProxyGroups":{"type":"object"},"BatchAddResponse":{"properties":{"data":{"$ref":"#/components/schemas/BatchAddResult"}},"required":["data"],"type":"object"},"BatchAddResult":{"properties":{"processedRequests":{"items":{"$ref":"#/components/schemas/AddedRequest"},"type":"array"},"unprocessedRequests":{"items":{"$ref":"#/components/schemas/UnprocessedRequest"},"type":"array"}},"required":["processedRequests","unprocessedRequests"],"type":"object"},"BatchDeleteResponse":{"properties":{"data":{"$ref":"#/components/schemas/BatchDeleteResult"}},"required":["data"],"type":"object"},"BatchDeleteResult":{"properties":{"processedRequests":{"items":{"$ref":"#/components/schemas/DeletedRequest"},"type":"array"},"unprocessedRequests":{"items":{"$ref":"#/components/schemas/UnprocessedRequest"},"type":"array"}},"required":["processedRequests","unprocessedRequests"],"type":"object"},"BrowserInfoResponse":{"properties":{"bodyLength":{"type":"integer"},"clientIp":{"type":["string","null"]},"countryCode":{"type":["string","null"]},"headers":{"type":"object"},"method":{"type":"string"},"rawHeaders":{"items":{"type":"string"},"type":"array"}},"required":["method","clientIp","countryCode","bodyLength"],"type":"object"},"Build":{"properties":{"actId":{"type":"string"},"actVersion":{"properties":{"actorPath":{"type":["string","null"]},"buildTag":{"type":["string","null"]},"gitHubGistUrl":{"type":"string"},"gitRepoUrl":{"type":"string"},"sourceFiles":{"items":{"$ref":"#/components/schemas/SourceCodeFile"},"type":"array"},"sourceType":{"$ref":"#/components/schemas/VersionSourceType"},"tarballUrl":{"type":"string"},"versionNumber":{"pattern":"^([0-9]|[1-9][0-9])\\.([0-9]|[1-9][0-9])$","type":"string"}},"type":"object"},"actorDefinition":{"anyOf":[{"$ref":"#/components/schemas/ActorDefinition"},{"type":"null"}]},"buildNumber":{"pattern":"^([0-9]|[1-9][0-9])\\.([0-9]|[1-9][0-9])(\\.[1-9][0-9]{0,4})$","type":"string"},"changelog":{"type":["string","null"]},"createdByOrganizationMemberUserId":{"type":"string"},"exitCode":{"type":["integer","null"]},"finishedAt":{"format":"date-time","type":["string","null"]},"gitBranchName":{"type":"string"},"gitCommitId":{"type":"string"},"id":{"type":"string"},"imageDigest":{"type":["string","null"]},"inputSchema":{"type":["string","null"]},"meta":{"$ref":"#/components/schemas/BuildMeta"},"options":{"anyOf":[{"$ref":"#/components/schemas/BuildOptions"},{"type":"null"}]},"readme":{"type":["string","null"]},"startedAt":{"format":"date-time","type":"string"},"stats":{"anyOf":[{"$ref":"#/components/schemas/BuildStats"},{"type":"null"}]},"status":{"$ref":"#/components/schemas/ActorJobStatus"},"usage":{"anyOf":[{"$ref":"#/components/schemas/BuildUsage"},{"type":"null"}]},"usageTotalUsd":{"type":["number","null"]},"usageUsd":{"anyOf":[{"$ref":"#/components/schemas/BuildUsage"},{"type":"null"}]},"userId":{"type":"string"}},"required":["id","actId","userId","startedAt","status","meta","buildNumber"],"type":"object"},"BuildListItem":{"properties":{"actId":{"type":"string"},"buildNumber":{"pattern":"^([0-9]|[1-9][0-9])\\.([0-9]|[1-9][0-9])(\\.[1-9][0-9]{0,4})$","type":"string"},"buildNumberInt":{"type":"integer"},"finishedAt":{"format":"date-time","type":["string","null"]},"id":{"type":"string"},"meta":{"$ref":"#/components/schemas/BuildMeta"},"startedAt":{"format":"date-time","type":"string"},"status":{"$ref":"#/components/schemas/ActorJobStatus"},"usageTotalUsd":{"type":"number"},"userId":{"type":"string"}},"required":["id","status","startedAt","buildNumber"],"type":"object"},"BuildMeta":{"properties":{"actorRunId":{"type":"string"},"clientIp":{"type":["string"]},"githubActionsRunUrl":{"type":"string"},"origin":{"$ref":"#/components/schemas/RunOrigin"},"userAgent":{"type":["string"]},"webhookDispatchId":{"type":"string"}},"required":["origin"],"type":"object"},"BuildOptions":{"properties":{"betaPackages":{"type":["boolean","null"]},"debug":{"type":["boolean","null"]},"diskMbytes":{"type":["integer","null"]},"memoryMbytes":{"type":["integer","null"]},"tag":{"type":["string","null"]},"useCache":{"type":["boolean","null"]}},"type":"object"},"BuildResponse":{"properties":{"data":{"$ref":"#/components/schemas/Build"}},"required":["data"],"type":"object"},"BuildStats":{"properties":{"computeUnits":{"type":"number"},"durationMillis":{"type":"integer"},"imageSizeBytes":{"type":["integer","null"]},"runTimeSecs":{"type":"number"}},"type":"object"},"BuildUsage":{"properties":{"ACTOR_COMPUTE_UNITS":{"type":["number","null"]}},"type":"object"},"CommonActorPricingInfo":{"properties":{"apifyMarginPercentage":{"type":"number"},"createdAt":{"format":"date-time","type":"string"},"forceContainsSignificantPriceChange":{"type":"boolean"},"isPriceChangeNotificationSuppressed":{"type":"boolean"},"notifiedAboutChangeAt":{"format":"date-time","type":["string","null"]},"notifiedAboutFutureChangeAt":{"format":"date-time","type":["string","null"]},"notifiedAboutRentalToPpePriceChangeAt":{"format":"date-time","type":["string","null"]},"reasonForChange":{"type":["string","null"]},"startedAt":{"format":"date-time","type":"string"}},"required":["apifyMarginPercentage","createdAt","startedAt"],"type":"object"},"CurrentPricingInfo":{"properties":{"apifyMarginPercentage":{"type":"number"},"createdAt":{"format":"date-time","type":"string"},"forceContainsSignificantPriceChange":{"type":"boolean"},"isPPEPlatformUsagePaidByUser":{"type":"boolean"},"isPriceChangeNotificationSuppressed":{"type":"boolean"},"minimalMaxTotalChargeUsd":{"type":["number","null"]},"notifiedAboutChangeAt":{"format":"date-time","type":["string","null"]},"notifiedAboutFutureChangeAt":{"format":"date-time","type":["string","null"]},"notifiedAboutRentalToPpePriceChangeAt":{"format":"date-time","type":["string","null"]},"pricePerUnitUsd":{"type":"number"},"pricingModel":{"type":"string"},"pricingPerEvent":{"type":["object","null"]},"reasonForChange":{"type":["string","null"]},"startedAt":{"format":"date-time","type":"string"},"tieredPricing":{"type":"object"},"trialMinutes":{"type":"integer"},"unitName":{"type":"string"}},"required":["pricingModel"],"type":"object"},"CurrentUsage":{"properties":{"activeActorJobCount":{"type":"integer"},"actorCount":{"type":"integer"},"actorMemoryGbytes":{"type":"number"},"actorTaskCount":{"type":"integer"},"monthlyActorComputeUnits":{"type":"number"},"monthlyExternalDataTransferGbytes":{"type":"number"},"monthlyProxySerps":{"type":"integer"},"monthlyResidentialProxyGbytes":{"type":"number"},"monthlyUnblockerUnits":{"type":"number"},"monthlyUsageUsd":{"type":"number"},"scheduleCount":{"type":"integer"},"teamAccountSeatCount":{"type":"integer"}},"required":["monthlyUsageUsd","monthlyActorComputeUnits","monthlyExternalDataTransferGbytes","monthlyProxySerps","monthlyResidentialProxyGbytes","actorMemoryGbytes","actorCount","actorTaskCount","activeActorJobCount","teamAccountSeatCount"],"type":"object"},"DailyServiceUsage":{"properties":{"date":{"format":"date-time","type":"string"},"serviceUsage":{"$ref":"#/components/schemas/ServiceUsage"},"totalUsageCreditsUsd":{"type":"number"}},"required":["date","serviceUsage","totalUsageCreditsUsd"],"type":"object"},"DatasetListItem":{"properties":{"accessedAt":{"format":"date-time","type":"string"},"actId":{"type":["string","null"]},"actRunId":{"type":["string","null"]},"cleanItemCount":{"type":"integer"},"createdAt":{"format":"date-time","type":"string"},"generalAccess":{"anyOf":[{"$ref":"#/components/schemas/GeneralAccess"},{"type":"null"}]},"id":{"type":"string"},"itemCount":{"type":"integer"},"modifiedAt":{"format":"date-time","type":"string"},"name":{"type":["string","null"]},"stats":{"$ref":"#/components/schemas/DatasetStats"},"title":{"type":["string","null"]},"userId":{"type":"string"},"username":{"type":"string"}},"required":["id","userId","createdAt","modifiedAt","accessedAt","itemCount","cleanItemCount"],"type":"object"},"DatasetResource":{"properties":{"accessedAt":{"format":"date-time","type":"string"},"actId":{"type":["string","null"]},"actRunId":{"type":["string","null"]},"cleanItemCount":{"minimum":0,"type":"integer"},"consoleUrl":{"format":"uri","type":"string"},"createdAt":{"format":"date-time","type":"string"},"fields":{"items":{"type":"string"},"type":["array","null"]},"generalAccess":{"anyOf":[{"$ref":"#/components/schemas/GeneralAccess"},{"type":"null"}]},"id":{"type":"string"},"itemCount":{"minimum":0,"type":"integer"},"itemsPublicUrl":{"format":"uri","type":"string"},"modifiedAt":{"format":"date-time","type":"string"},"name":{"type":["string","null"]},"schema":{"type":["object","null"]},"stats":{"$ref":"#/components/schemas/DatasetStats"},"title":{"type":["string","null"]},"urlSigningSecretKey":{"type":["string","null"]},"userId":{"type":"string"}},"required":["id","userId","createdAt","modifiedAt","accessedAt","itemCount","cleanItemCount","consoleUrl"],"type":"object"},"DatasetResponse":{"properties":{"data":{"$ref":"#/components/schemas/DatasetResource"}},"required":["data"],"type":"object"},"DatasetStatistics":{"properties":{"fieldStatistics":{"type":["object","null"]}},"type":"object"},"DatasetStatisticsResponse":{"properties":{"data":{"$ref":"#/components/schemas/DatasetStatistics"}},"required":["data"],"type":"object"},"DatasetStats":{"properties":{"inflatedBytes":{"type":"integer"},"readCount":{"type":"integer"},"storageBytes":{"type":"integer"},"writeCount":{"type":"integer"}},"type":"object"},"DecodeAndVerifyResponse":{"properties":{"data":{"$ref":"#/components/schemas/DecodeAndVerifyResult"}},"required":["data"],"type":"object"},"DecodeAndVerifyResult":{"properties":{"decoded":{},"encodedByUserId":{"type":["string","null"]},"isVerifiedUser":{"type":"boolean"}},"required":["decoded","encodedByUserId","isVerifiedUser"],"type":"object"},"DefaultRunOptions":{"properties":{"build":{"type":"string"},"forcePermissionLevel":{"anyOf":[{"$ref":"#/components/schemas/ActorPermissionLevel"},{"type":"null"}]},"isMaxTotalChargeUsdSetByUser":{"type":["boolean","null"]},"maxItems":{"type":["integer","null"]},"maxTotalChargeUsd":{"type":["number","null"]},"memoryMbytes":{"type":"integer"},"restartOnError":{"type":["boolean","null"]},"timeoutSecs":{"type":"integer"}},"type":"object"},"DeletedRequest":{"anyOf":[{"$ref":"#/components/schemas/DeletedRequestById"},{"$ref":"#/components/schemas/DeletedRequestByUniqueKey"}]},"DeletedRequestById":{"properties":{"id":{"$ref":"#/components/schemas/RequestId"},"uniqueKey":{"$ref":"#/components/schemas/UniqueKey"}},"required":["id"],"type":"object"},"DeletedRequestByUniqueKey":{"properties":{"id":{"$ref":"#/components/schemas/RequestId"},"uniqueKey":{"$ref":"#/components/schemas/UniqueKey"}},"required":["uniqueKey"],"type":"object"},"EffectivePlatformFeature":{"properties":{"disabledReason":{"type":["string","null"]},"disabledReasonType":{"type":["string","null"]},"isEnabled":{"type":"boolean"},"isTrial":{"type":"boolean"},"trialExpirationAt":{"format":"date-time","type":["string","null"]}},"required":["isEnabled","disabledReason","disabledReasonType","isTrial","trialExpirationAt"],"type":"object"},"EffectivePlatformFeatures":{"properties":{"ACTORS":{"$ref":"#/components/schemas/EffectivePlatformFeature"},"ACTORS_PUBLIC_ALL":{"$ref":"#/components/schemas/EffectivePlatformFeature"},"ACTORS_PUBLIC_DEVELOPER":{"$ref":"#/components/schemas/EffectivePlatformFeature"},"PROXY":{"$ref":"#/components/schemas/EffectivePlatformFeature"},"PROXY_EXTERNAL_ACCESS":{"$ref":"#/components/schemas/EffectivePlatformFeature"},"PROXY_RESIDENTIAL":{"$ref":"#/components/schemas/EffectivePlatformFeature"},"PROXY_SERPS":{"$ref":"#/components/schemas/EffectivePlatformFeature"},"SCHEDULER":{"$ref":"#/components/schemas/EffectivePlatformFeature"},"STORAGE":{"$ref":"#/components/schemas/EffectivePlatformFeature"},"WEBHOOKS":{"$ref":"#/components/schemas/EffectivePlatformFeature"}},"required":["ACTORS","STORAGE","SCHEDULER","PROXY","PROXY_EXTERNAL_ACCESS","PROXY_RESIDENTIAL","PROXY_SERPS","WEBHOOKS","ACTORS_PUBLIC_ALL","ACTORS_PUBLIC_DEVELOPER"],"type":"object"},"EncodeAndSignResponse":{"properties":{"data":{"$ref":"#/components/schemas/EncodeAndSignResult"}},"required":["data"],"type":"object"},"EncodeAndSignResult":{"properties":{"encoded":{"type":"string"}},"required":["encoded"],"type":"object"},"EnvVar":{"properties":{"isSecret":{"type":["boolean","null"]},"name":{"type":"string"},"value":{"type":"string"},"valueHash":{"type":"string"}},"required":["name"],"type":"object"},"EnvVarResponse":{"properties":{"data":{"$ref":"#/components/schemas/EnvVar"}},"required":["data"],"type":"object"},"ExampleRunInput":{"properties":{"body":{"type":"string"},"contentType":{"type":"string"}},"type":"object"},"FlatPricePerMonthActorPricingInfo":{"allOf":[{"$ref":"#/components/schemas/CommonActorPricingInfo"},{"properties":{"pricePerUnitUsd":{"type":"number"},"pricingModel":{"type":"string"},"trialMinutes":{"type":"integer"}},"required":["pricingModel","pricePerUnitUsd","trialMinutes"],"type":"object"}]},"FreeActorPricingInfo":{"allOf":[{"$ref":"#/components/schemas/CommonActorPricingInfo"},{"properties":{"pricingModel":{"type":"string"}},"required":["pricingModel"],"type":"object"}]},"GeneralAccess":{"type":"string"},"GitProviderIntegration":{"properties":{"addWorkspaceUrl":{"type":"string"},"id":{"type":"string"},"provider":{"type":"string"},"workspaces":{"items":{"$ref":"#/components/schemas/GitProviderWorkspace"},"type":"array"}},"required":["id","provider","workspaces"],"type":"object"},"GitProviderWorkspace":{"properties":{"id":{"type":"string"},"label":{"type":"string"}},"required":["id","label"],"type":"object"},"GitRepo":{"properties":{"htmlUrl":{"type":"string"},"httpsUrl":{"type":"string"},"sshUrl":{"type":"string"}},"required":["sshUrl","httpsUrl","htmlUrl"],"type":"object"},"GitRepoResponse":{"properties":{"data":{"$ref":"#/components/schemas/GitRepo"}},"required":["data"],"type":"object"},"HadMultipleClients":{"type":"boolean"},"HandledRequestCount":{"minimum":0,"type":"integer"},"HeadLimit":{"type":"integer"},"HttpMethod":{"type":"string"},"IntegrationTracking":{"properties":{"appId":{"type":"string"},"isAiTool":{"type":"boolean"},"platform":{"type":"string"}},"type":"object"},"KeyValueStoreKey":{"properties":{"key":{"type":"string"},"recordPublicUrl":{"format":"uri","type":"string"},"size":{"type":"integer"}},"required":["key","size","recordPublicUrl"],"type":"object"},"KeyValueStoreListItem":{"properties":{"accessedAt":{"format":"date-time","type":"string"},"actId":{"type":["string","null"]},"actRunId":{"type":["string","null"]},"createdAt":{"format":"date-time","type":"string"},"generalAccess":{"anyOf":[{"$ref":"#/components/schemas/GeneralAccess"},{"type":"null"}]},"id":{"type":"string"},"modifiedAt":{"format":"date-time","type":"string"},"name":{"type":["string","null"]},"schema":{"type":["object","string","null"]},"stats":{"$ref":"#/components/schemas/KeyValueStoreListStats"},"title":{"type":["string","null"]},"userId":{"type":["string","null"]},"username":{"type":["string","null"]}},"required":["id","createdAt","modifiedAt","accessedAt"],"type":"object"},"KeyValueStoreListStats":{"properties":{"deleteCount":{"type":"integer"},"listCount":{"type":"integer"},"readCount":{"type":"integer"},"s3StorageBytes":{"type":"integer"},"writeCount":{"type":"integer"}},"type":"object"},"KeyValueStoreResource":{"properties":{"accessedAt":{"format":"date-time","type":"string"},"actId":{"type":["string","null"]},"actRunId":{"type":["string","null"]},"consoleUrl":{"format":"uri","type":"string"},"createdAt":{"format":"date-time","type":"string"},"generalAccess":{"anyOf":[{"$ref":"#/components/schemas/GeneralAccess"},{"type":"null"}]},"id":{"type":"string"},"keysPublicUrl":{"format":"uri","type":"string"},"modifiedAt":{"format":"date-time","type":"string"},"name":{"type":["string","null"]},"recordsPublicUrl":{"format":"uri","type":"string"},"schema":{"type":["object","null"]},"stats":{"$ref":"#/components/schemas/KeyValueStoreStats"},"title":{"type":["string","null"]},"urlSigningSecretKey":{"type":["string","null"]},"userId":{"type":["string","null"]},"username":{"type":["string","null"]}},"required":["id","createdAt","modifiedAt","accessedAt"],"type":"object"},"KeyValueStoreResponse":{"properties":{"data":{"$ref":"#/components/schemas/KeyValueStoreResource"}},"required":["data"],"type":"object"},"KeyValueStoreStats":{"properties":{"deleteCount":{"type":"integer"},"listCount":{"type":"integer"},"readCount":{"type":"integer"},"s3StorageBytes":{"type":"integer"},"storageBytes":{"type":"integer"},"writeCount":{"type":"integer"}},"type":"object"},"Limits":{"properties":{"dataRetentionDays":{"type":"integer"},"maxActorCount":{"type":"integer"},"maxActorMemoryGbytes":{"type":"number"},"maxActorTaskCount":{"type":"integer"},"maxConcurrentActorJobs":{"type":"integer"},"maxMonthlyActorComputeUnits":{"type":"number"},"maxMonthlyExternalDataTransferGbytes":{"type":"number"},"maxMonthlyProxySerps":{"type":"integer"},"maxMonthlyResidentialProxyGbytes":{"type":"number"},"maxMonthlyUnblockerUnits":{"type":"number"},"maxMonthlyUsageUsd":{"type":"number"},"maxScheduleCount":{"type":"integer"},"maxTeamAccountSeatCount":{"type":"integer"}},"required":["maxMonthlyUsageUsd","maxMonthlyActorComputeUnits","maxMonthlyExternalDataTransferGbytes","maxMonthlyProxySerps","maxMonthlyResidentialProxyGbytes","maxActorMemoryGbytes","maxActorCount","maxActorTaskCount","maxConcurrentActorJobs","maxTeamAccountSeatCount","dataRetentionDays"],"type":"object"},"ListOfActorOAuthAccounts":{"properties":{"items":{"items":{"$ref":"#/components/schemas/ActorOAuthAccount"},"type":"array"},"total":{"type":"integer"}},"required":["total","items"],"type":"object"},"ListOfActorOAuthAccountsResponse":{"properties":{"data":{"$ref":"#/components/schemas/ListOfActorOAuthAccounts"}},"required":["data"],"type":"object"},"ListOfActorOAuthConnections":{"properties":{"items":{"items":{"$ref":"#/components/schemas/ActorOAuthConnection"},"type":"array"},"total":{"type":"integer"}},"required":["total","items"],"type":"object"},"ListOfActorOAuthConnectionsResponse":{"properties":{"data":{"$ref":"#/components/schemas/ListOfActorOAuthConnections"}},"required":["data"],"type":"object"},"ListOfActors":{"allOf":[{"$ref":"#/components/schemas/PaginationResponse"},{"properties":{"items":{"items":{"$ref":"#/components/schemas/ActorListItem"},"type":"array"}},"required":["items"],"type":"object"}]},"ListOfActorsResponse":{"properties":{"data":{"$ref":"#/components/schemas/ListOfActors"}},"required":["data"],"type":"object"},"ListOfBuilds":{"allOf":[{"$ref":"#/components/schemas/PaginationResponse"},{"properties":{"items":{"items":{"$ref":"#/components/schemas/BuildListItem"},"type":"array"}},"required":["items"],"type":"object"}]},"ListOfBuildsResponse":{"properties":{"data":{"$ref":"#/components/schemas/ListOfBuilds"}},"required":["data"],"type":"object"},"ListOfDatasets":{"allOf":[{"$ref":"#/components/schemas/PaginationResponse"},{"properties":{"items":{"items":{"$ref":"#/components/schemas/DatasetListItem"},"type":"array"},"unnamed":{"type":"boolean"}},"required":["items"],"type":"object"}]},"ListOfDatasetsResponse":{"properties":{"data":{"$ref":"#/components/schemas/ListOfDatasets"}},"required":["data"],"type":"object"},"ListOfEnvVars":{"properties":{"items":{"items":{"$ref":"#/components/schemas/EnvVar"},"type":"array"},"total":{"type":"integer"}},"required":["total","items"],"type":"object"},"ListOfEnvVarsResponse":{"properties":{"data":{"$ref":"#/components/schemas/ListOfEnvVars"}},"required":["data"],"type":"object"},"ListOfGitProviderIntegrationsResponse":{"properties":{"data":{"items":{"$ref":"#/components/schemas/GitProviderIntegration"},"type":"array"}},"required":["data"],"type":"object"},"ListOfKeyValueStores":{"allOf":[{"$ref":"#/components/schemas/PaginationResponse"},{"properties":{"items":{"items":{"$ref":"#/components/schemas/KeyValueStoreListItem"},"type":"array"},"unnamed":{"type":"boolean"}},"required":["items"],"type":"object"}]},"ListOfKeyValueStoresResponse":{"properties":{"data":{"$ref":"#/components/schemas/ListOfKeyValueStores"}},"required":["data"],"type":"object"},"ListOfKeys":{"properties":{"count":{"type":"integer"},"exclusiveStartKey":{"type":["string","null"]},"isTruncated":{"type":"boolean"},"items":{"items":{"$ref":"#/components/schemas/KeyValueStoreKey"},"type":"array"},"limit":{"type":"integer"},"nextExclusiveStartKey":{"type":["string","null"]}},"required":["items","count","limit","isTruncated"],"type":"object"},"ListOfKeysResponse":{"properties":{"data":{"$ref":"#/components/schemas/ListOfKeys"}},"required":["data"],"type":"object"},"ListOfRequestQueues":{"allOf":[{"$ref":"#/components/schemas/PaginationResponse"},{"properties":{"items":{"items":{"$ref":"#/components/schemas/RequestQueueListItem"},"type":"array"},"unnamed":{"type":"boolean"}},"required":["items"],"type":"object"}]},"ListOfRequestQueuesResponse":{"properties":{"data":{"$ref":"#/components/schemas/ListOfRequestQueues"}},"required":["data"],"type":"object"},"ListOfRequests":{"properties":{"cursor":{"type":"string"},"exclusiveStartId":{"type":"string"},"items":{"items":{"$ref":"#/components/schemas/RequestResource"},"type":"array"},"limit":{"type":"integer"},"nextCursor":{"type":"string"}},"required":["items","limit"],"type":"object"},"ListOfRequestsResponse":{"properties":{"data":{"$ref":"#/components/schemas/ListOfRequests"}},"required":["data"],"type":"object"},"ListOfRuns":{"allOf":[{"$ref":"#/components/schemas/PaginationResponse"},{"properties":{"items":{"items":{"$ref":"#/components/schemas/RunListItem"},"type":"array"}},"required":["items"],"type":"object"}]},"ListOfRunsResponse":{"properties":{"data":{"$ref":"#/components/schemas/ListOfRuns"}},"required":["data"],"type":"object"},"ListOfSchedules":{"allOf":[{"$ref":"#/components/schemas/PaginationResponse"},{"properties":{"items":{"items":{"$ref":"#/components/schemas/ScheduleListItem"},"type":"array"}},"required":["items"],"type":"object"}]},"ListOfSchedulesResponse":{"properties":{"data":{"$ref":"#/components/schemas/ListOfSchedules"}},"required":["data"],"type":"object"},"ListOfStoreActors":{"allOf":[{"$ref":"#/components/schemas/PaginationResponse"},{"properties":{"items":{"items":{"$ref":"#/components/schemas/StoreActor"},"type":"array"}},"required":["items"],"type":"object"}]},"ListOfStoreActorsResponse":{"properties":{"data":{"$ref":"#/components/schemas/ListOfStoreActors"}},"required":["data"],"type":"object"},"ListOfTasks":{"allOf":[{"$ref":"#/components/schemas/PaginationResponse"},{"properties":{"items":{"items":{"$ref":"#/components/schemas/TaskListItem"},"type":"array"}},"required":["items"],"type":"object"}]},"ListOfTasksResponse":{"properties":{"data":{"$ref":"#/components/schemas/ListOfTasks"}},"required":["data"],"type":"object"},"ListOfVersions":{"properties":{"items":{"items":{"$ref":"#/components/schemas/Version"},"type":"array"},"total":{"type":"integer"}},"required":["total","items"],"type":"object"},"ListOfVersionsResponse":{"properties":{"data":{"$ref":"#/components/schemas/ListOfVersions"}},"required":["data"],"type":"object"},"ListOfWebhookDispatches":{"allOf":[{"$ref":"#/components/schemas/PaginationResponse"},{"properties":{"items":{"items":{"$ref":"#/components/schemas/WebhookDispatch"},"type":"array"}},"required":["items"],"type":"object"}]},"ListOfWebhookDispatchesResponse":{"properties":{"data":{"$ref":"#/components/schemas/ListOfWebhookDispatches"}},"required":["data"],"type":"object"},"ListOfWebhookResources":{"allOf":[{"$ref":"#/components/schemas/PaginationResponse"},{"properties":{"items":{"items":{"$ref":"#/components/schemas/WebhookResource"},"type":"array"}},"required":["items"],"type":"object"}]},"ListOfWebhookResourcesResponse":{"properties":{"data":{"$ref":"#/components/schemas/ListOfWebhookResources"}},"required":["data"],"type":"object"},"ListOfWebhooks":{"allOf":[{"$ref":"#/components/schemas/PaginationResponse"},{"properties":{"items":{"items":{"$ref":"#/components/schemas/WebhookListItem"},"type":"array"}},"required":["items"],"type":"object"}]},"ListOfWebhooksResponse":{"properties":{"data":{"$ref":"#/components/schemas/ListOfWebhooks"}},"required":["data"],"type":"object"},"LockExpiresAt":{"format":"date-time","type":"string"},"LockedRequestQueueHead":{"properties":{"clientKey":{"type":"string"},"hadMultipleClients":{"$ref":"#/components/schemas/HadMultipleClients"},"items":{"items":{"$ref":"#/components/schemas/LockedRequestQueueHeadItem"},"type":"array"},"limit":{"$ref":"#/components/schemas/HeadLimit"},"lockSecs":{"type":"integer"},"queueHasLockedRequests":{"type":"boolean"},"queueModifiedAt":{"$ref":"#/components/schemas/QueueModifiedAt"}},"required":["limit","queueModifiedAt","hadMultipleClients","lockSecs","items"],"type":"object"},"LockedRequestQueueHeadItem":{"properties":{"id":{"$ref":"#/components/schemas/RequestId"},"lockExpiresAt":{"$ref":"#/components/schemas/LockExpiresAt"},"method":{"$ref":"#/components/schemas/RequestMethod"},"retryCount":{"$ref":"#/components/schemas/RetryCount"},"uniqueKey":{"$ref":"#/components/schemas/UniqueKey"},"url":{"$ref":"#/components/schemas/RequestUrl"}},"required":["id","uniqueKey","url","lockExpiresAt"],"type":"object"},"LockedRequestQueueHeadResponse":{"properties":{"data":{"$ref":"#/components/schemas/LockedRequestQueueHead"}},"required":["data"],"type":"object"},"MonthlyServiceUsage":{"type":"object"},"MonthlyUsage":{"properties":{"dailyServiceUsages":{"items":{"$ref":"#/components/schemas/DailyServiceUsage"},"type":"array"},"monthlyServiceUsage":{"$ref":"#/components/schemas/MonthlyServiceUsage"},"totalUsageCreditsUsdAfterVolumeDiscount":{"type":"number"},"totalUsageCreditsUsdBeforeVolumeDiscount":{"type":"number"},"usageCycle":{"$ref":"#/components/schemas/UsageCycle"}},"required":["usageCycle","monthlyServiceUsage","dailyServiceUsages","totalUsageCreditsUsdBeforeVolumeDiscount","totalUsageCreditsUsdAfterVolumeDiscount"],"type":"object"},"MonthlyUsageResponse":{"properties":{"data":{"$ref":"#/components/schemas/MonthlyUsage"}},"required":["data"],"type":"object"},"PaginationResponse":{"properties":{"count":{"minimum":0,"type":"integer"},"desc":{"type":"boolean"},"limit":{"minimum":1,"type":"integer"},"offset":{"minimum":0,"type":"integer"},"total":{"minimum":0,"type":"integer"}},"required":["total","offset","limit","desc","count"],"type":"object"},"PayPerEventActorPricingInfo":{"allOf":[{"$ref":"#/components/schemas/CommonActorPricingInfo"},{"properties":{"isPPEPlatformUsagePaidByUser":{"type":["boolean","null"]},"minimalMaxTotalChargeUsd":{"type":["number","null"]},"pricingModel":{"type":"string"},"pricingPerEvent":{"properties":{"actorChargeEvents":{"type":"object"}},"type":"object"}},"required":["pricingModel","pricingPerEvent"],"type":"object"}]},"PendingRequestCount":{"type":"integer"},"PricePerDatasetItemActorPricingInfo":{"allOf":[{"$ref":"#/components/schemas/CommonActorPricingInfo"},{"properties":{"pricePerUnitUsd":{"type":"number"},"pricingModel":{"type":"string"},"tieredPricing":{"$ref":"#/components/schemas/TieredPricingPerDatasetItem"},"unitName":{"type":"string"}},"required":["pricingModel","unitName"],"type":"object"}]},"ProlongRequestLockResponse":{"properties":{"data":{"$ref":"#/components/schemas/RequestLockInfo"}},"required":["data"],"type":"object"},"ProxyGroup":{"properties":{"availableCount":{"type":"integer"},"description":{"type":["string","null"]},"id":{"type":"string"},"name":{"type":"string"}},"required":["name","description"],"type":"object"},"ProxyResource":{"properties":{"groups":{"items":{"$ref":"#/components/schemas/ProxyGroup"},"type":"array"},"password":{"type":["string","null"]}},"required":["groups"],"type":"object"},"QueueAccessedAt":{"format":"date-time","type":"string"},"QueueCreatedAt":{"format":"date-time","type":"string"},"QueueId":{"type":"string"},"QueueModifiedAt":{"format":"date-time","type":"string"},"QueueUserId":{"type":"string"},"RecordResponse":{"type":"object"},"RegisterDeploymentKeyResponse":{"properties":{"data":{"properties":{"repoName":{"type":"string"}},"required":["repoName"],"type":"object"}},"required":["data"],"type":"object"},"RequestBase":{"properties":{"errorMessages":{"items":{"type":"string"},"type":["array","null"]},"handledAt":{"format":"date-time","type":["string","null"]},"headers":{"type":["object","null"]},"loadedUrl":{"type":["string","null"]},"method":{"$ref":"#/components/schemas/RequestMethod"},"noRetry":{"type":["boolean","null"]},"payload":{"type":["string","object","null"]},"retryCount":{"$ref":"#/components/schemas/RetryCount"},"uniqueKey":{"$ref":"#/components/schemas/UniqueKey"},"url":{"$ref":"#/components/schemas/RequestUrl"},"userData":{"$ref":"#/components/schemas/RequestUserData"}},"required":["uniqueKey","url"],"type":"object"},"RequestId":{"type":"string"},"RequestLockInfo":{"properties":{"lockExpiresAt":{"$ref":"#/components/schemas/LockExpiresAt"}},"required":["lockExpiresAt"],"type":"object"},"RequestMethod":{"$ref":"#/components/schemas/HttpMethod"},"RequestQueueHead":{"properties":{"hadMultipleClients":{"$ref":"#/components/schemas/HadMultipleClients"},"items":{"items":{"$ref":"#/components/schemas/RequestQueueHeadItem"},"type":"array"},"limit":{"$ref":"#/components/schemas/HeadLimit"},"queueModifiedAt":{"$ref":"#/components/schemas/QueueModifiedAt"}},"required":["limit","queueModifiedAt","hadMultipleClients","items"],"type":"object"},"RequestQueueHeadItem":{"properties":{"id":{"$ref":"#/components/schemas/RequestId"},"method":{"$ref":"#/components/schemas/RequestMethod"},"retryCount":{"$ref":"#/components/schemas/RetryCount"},"uniqueKey":{"$ref":"#/components/schemas/UniqueKey"},"url":{"$ref":"#/components/schemas/RequestUrl"}},"required":["id","uniqueKey","url"],"type":"object"},"RequestQueueHeadResponse":{"properties":{"data":{"$ref":"#/components/schemas/RequestQueueHead"}},"required":["data"],"type":"object"},"RequestQueueListItem":{"properties":{"accessedAt":{"$ref":"#/components/schemas/QueueAccessedAt"},"actId":{"type":["string","null"]},"actRunId":{"type":["string","null"]},"createdAt":{"$ref":"#/components/schemas/QueueCreatedAt"},"expireAt":{"format":"date-time","type":"string"},"generalAccess":{"anyOf":[{"$ref":"#/components/schemas/GeneralAccess"},{"type":"null"}]},"hadMultipleClients":{"$ref":"#/components/schemas/HadMultipleClients"},"handledRequestCount":{"$ref":"#/components/schemas/HandledRequestCount"},"id":{"$ref":"#/components/schemas/QueueId"},"modifiedAt":{"$ref":"#/components/schemas/QueueModifiedAt"},"name":{"type":["string","null"]},"pendingRequestCount":{"$ref":"#/components/schemas/PendingRequestCount"},"stats":{"$ref":"#/components/schemas/RequestQueueListStats"},"title":{"type":["string","null"]},"totalRequestCount":{"$ref":"#/components/schemas/TotalRequestCount"},"userId":{"$ref":"#/components/schemas/QueueUserId"},"username":{"type":"string"}},"required":["id","userId","createdAt","modifiedAt","accessedAt","totalRequestCount","handledRequestCount","pendingRequestCount","hadMultipleClients"],"type":"object"},"RequestQueueListStats":{"properties":{"deleteCount":{"type":"integer"},"dynamodbStorageBytes":{"type":"integer"},"headItemReadCount":{"type":"integer"},"readCount":{"type":"integer"},"s3StorageBytes":{"type":"integer"},"writeCount":{"type":"integer"}},"type":"object"},"RequestQueueResource":{"properties":{"accessedAt":{"$ref":"#/components/schemas/QueueAccessedAt"},"actId":{"type":["string","null"]},"actRunId":{"type":["string","null"]},"consoleUrl":{"format":"uri","type":"string"},"createdAt":{"$ref":"#/components/schemas/QueueCreatedAt"},"generalAccess":{"anyOf":[{"$ref":"#/components/schemas/GeneralAccess"},{"type":"null"}]},"hadMultipleClients":{"$ref":"#/components/schemas/HadMultipleClients"},"handledRequestCount":{"$ref":"#/components/schemas/HandledRequestCount"},"id":{"$ref":"#/components/schemas/QueueId"},"modifiedAt":{"$ref":"#/components/schemas/QueueModifiedAt"},"name":{"type":["string","null"]},"pendingRequestCount":{"$ref":"#/components/schemas/PendingRequestCount"},"stats":{"$ref":"#/components/schemas/RequestQueueStats"},"title":{"type":["string","null"]},"totalRequestCount":{"$ref":"#/components/schemas/TotalRequestCount"},"userId":{"$ref":"#/components/schemas/QueueUserId"}},"required":["id","userId","createdAt","modifiedAt","accessedAt","totalRequestCount","handledRequestCount","pendingRequestCount","hadMultipleClients","consoleUrl"],"type":"object"},"RequestQueueResponse":{"properties":{"data":{"$ref":"#/components/schemas/RequestQueueResource"}},"required":["data"],"type":"object"},"RequestQueueStats":{"properties":{"deleteCount":{"type":"integer"},"headItemReadCount":{"type":"integer"},"readCount":{"type":"integer"},"storageBytes":{"type":"integer"},"writeCount":{"type":"integer"}},"type":"object"},"RequestRegistration":{"properties":{"requestId":{"$ref":"#/components/schemas/RequestId"},"wasAlreadyHandled":{"$ref":"#/components/schemas/WasAlreadyHandled"},"wasAlreadyPresent":{"$ref":"#/components/schemas/WasAlreadyPresent"}},"required":["requestId","wasAlreadyPresent","wasAlreadyHandled"],"type":"object"},"RequestResource":{"allOf":[{"$ref":"#/components/schemas/RequestBase","required":["id"]},{"properties":{"id":{"$ref":"#/components/schemas/RequestId"},"lockByClient":{"type":"string"},"lockExpiresAt":{"$ref":"#/components/schemas/LockExpiresAt"}}}]},"RequestResponse":{"properties":{"data":{"$ref":"#/components/schemas/RequestResource"}},"required":["data"],"type":"object"},"RequestUrl":{"type":"string"},"RequestUserData":{"type":"object"},"RetryCount":{"type":"integer"},"Run":{"properties":{"accountedChargedEventCounts":{"type":["object","null"]},"actId":{"type":"string"},"actorTaskId":{"type":["string","null"]},"buildId":{"type":"string"},"buildNumber":{"pattern":"^([0-9]|[1-9][0-9])\\.([0-9]|[1-9][0-9])(\\.[1-9][0-9]{0,4})$","type":["string","null"]},"chargedEventCounts":{"type":"object"},"consoleUrl":{"format":"uri","type":"string"},"containerUrl":{"format":"uri","type":"string"},"createdByOrganizationMemberUserId":{"type":"string"},"defaultDatasetId":{"type":"string"},"defaultKeyValueStoreId":{"type":"string"},"defaultRequestQueueId":{"type":"string"},"eventUsage":{"type":"object"},"exitCode":{"type":["integer","null"]},"externalChargedTotalUsd":{"type":"number"},"finishedAt":{"format":"date-time","type":["string","null"]},"generalAccess":{"anyOf":[{"$ref":"#/components/schemas/GeneralAccess"},{"type":"null"}]},"gitBranchName":{"type":["string","null"]},"id":{"type":"string"},"integrationTracking":{"$ref":"#/components/schemas/IntegrationTracking"},"isContainerServerReady":{"type":["boolean","null"]},"isStatusMessageTerminal":{"type":["boolean","null"]},"meta":{"$ref":"#/components/schemas/RunMeta"},"metamorphs":{"anyOf":[{"items":{"$ref":"#/components/schemas/RunMetamorphEvent"},"type":"array"},{"type":"null"}]},"options":{"$ref":"#/components/schemas/RunOptions"},"output":{"type":"object"},"platformUsageBillingModel":{"type":"string"},"pricingInfo":{"$ref":"#/components/schemas/ActorRunPricingInfo"},"standby":{"anyOf":[{"$ref":"#/components/schemas/RunStandby"},{"type":"null"}]},"startedAt":{"format":"date-time","type":"string"},"stats":{"$ref":"#/components/schemas/RunStats"},"status":{"$ref":"#/components/schemas/ActorJobStatus"},"statusMessage":{"type":["string","null"]},"storageIds":{"properties":{"datasets":{"properties":{"default":{"type":"string"}},"type":"object"},"keyValueStores":{"properties":{"default":{"type":"string"}},"type":"object"},"requestQueues":{"properties":{"default":{"type":"string"}},"type":"object"}},"type":"object"},"usage":{"anyOf":[{"$ref":"#/components/schemas/RunUsage"},{"type":"null"}]},"usageTotalUsd":{"type":["number","null"]},"usageUsd":{"anyOf":[{"$ref":"#/components/schemas/RunUsageUsd"},{"type":"null"}]},"userId":{"type":"string"}},"required":["id","actId","userId","startedAt","status","meta","stats","options","buildId","defaultKeyValueStoreId","defaultDatasetId","defaultRequestQueueId"],"type":"object"},"RunListItem":{"properties":{"actId":{"type":"string"},"actorTaskId":{"type":["string","null"]},"buildId":{"type":"string"},"buildNumber":{"pattern":"^([0-9]|[1-9][0-9])\\.([0-9]|[1-9][0-9])(\\.[1-9][0-9]{0,4})$","type":"string"},"buildNumberInt":{"type":"integer"},"defaultDatasetId":{"type":"string"},"defaultKeyValueStoreId":{"type":"string"},"defaultRequestQueueId":{"type":"string"},"finishedAt":{"format":"date-time","type":["string","null"]},"id":{"type":"string"},"meta":{"$ref":"#/components/schemas/RunMeta"},"standby":{"anyOf":[{"$ref":"#/components/schemas/RunStandby"},{"type":"null"}]},"startedAt":{"format":"date-time","type":"string"},"status":{"$ref":"#/components/schemas/ActorJobStatus"},"usageTotalUsd":{"type":"number"},"userId":{"type":"string"}},"required":["id","actId","status","startedAt","buildId","meta","usageTotalUsd","defaultKeyValueStoreId","defaultDatasetId","defaultRequestQueueId"],"type":"object"},"RunMeta":{"properties":{"actorRunId":{"type":"string"},"clientIp":{"type":["string","null"]},"origin":{"$ref":"#/components/schemas/RunOrigin"},"scheduleId":{"type":["string","null"]},"scheduledAct2Id":{"type":"string"},"scheduledActorTaskId":{"type":"string"},"scheduledAt":{"format":"date-time","type":["string","null"]},"userAgent":{"type":["string","null"]},"webhookDispatchId":{"type":"string"}},"required":["origin"],"type":"object"},"RunMetamorphEvent":{"properties":{"actorId":{"type":"string"},"buildId":{"type":"string"},"buildTags":{"items":{"type":"string"},"type":"array"},"createdAt":{"format":"date-time","type":"string"},"inputKey":{"type":["string","null"]}},"required":["createdAt","actorId","buildId"],"type":"object"},"RunOptions":{"properties":{"build":{"type":"string"},"diskMbytes":{"minimum":0,"type":"integer"},"isMaxTotalChargeUsdSetByUser":{"type":"boolean"},"maxItems":{"minimum":0,"type":["integer","null"]},"maxTotalChargeUsd":{"minimum":0,"type":["number","null"]},"memoryMbytes":{"maximum":32768,"minimum":128,"type":"integer"},"restartOnError":{"type":["boolean","null"]},"timeoutSecs":{"minimum":0,"type":"integer"}},"required":["build","timeoutSecs","memoryMbytes","diskMbytes"],"type":"object"},"RunOrigin":{"type":"string"},"RunResponse":{"properties":{"data":{"$ref":"#/components/schemas/Run"}},"required":["data"],"type":"object"},"RunStandby":{"properties":{"deployment":{"type":"string"}},"required":["deployment"],"type":"object"},"RunStats":{"properties":{"computeUnits":{"minimum":0,"type":"number"},"cpuAvgUsage":{"type":"number"},"cpuCurrentUsage":{"type":"number"},"cpuMaxUsage":{"type":"number"},"durationMillis":{"minimum":0,"type":"integer"},"inputBodyLen":{"minimum":0,"type":["integer","null"]},"memAvgBytes":{"type":"number"},"memCurrentBytes":{"minimum":0,"type":"integer"},"memMaxBytes":{"minimum":0,"type":"integer"},"metamorph":{"minimum":0,"type":"integer"},"migrationCount":{"minimum":0,"type":"integer"},"netRxBytes":{"minimum":0,"type":"integer"},"netTxBytes":{"minimum":0,"type":"integer"},"rebootCount":{"minimum":0,"type":"integer"},"restartCount":{"minimum":0,"type":"integer"},"resurrectCount":{"minimum":0,"type":"integer"},"runTimeSecs":{"minimum":0,"type":"number"}},"type":"object"},"RunUsage":{"properties":{"ACTOR_COMPUTE_UNITS":{"type":["number","null"]},"DATASET_READS":{"type":["integer","null"]},"DATASET_WRITES":{"type":["integer","null"]},"DATA_TRANSFER_EXTERNAL_GBYTES":{"type":["number","null"]},"DATA_TRANSFER_INTERNAL_GBYTES":{"type":["number","null"]},"KEY_VALUE_STORE_LISTS":{"type":["integer","null"]},"KEY_VALUE_STORE_READS":{"type":["integer","null"]},"KEY_VALUE_STORE_WRITES":{"type":["integer","null"]},"PROXY_RESIDENTIAL_TRANSFER_GBYTES":{"type":["number","null"]},"PROXY_SERPS":{"type":["integer","null"]},"PROXY_UNBLOCKER_UNITS":{"type":["number","null"]},"REQUEST_QUEUE_READS":{"type":["integer","null"]},"REQUEST_QUEUE_WRITES":{"type":["integer","null"]}},"type":"object"},"RunUsageUsd":{"properties":{"ACTOR_COMPUTE_UNITS":{"type":["number","null"]},"DATASET_READS":{"type":["number","null"]},"DATASET_WRITES":{"type":["number","null"]},"DATA_TRANSFER_EXTERNAL_GBYTES":{"type":["number","null"]},"DATA_TRANSFER_INTERNAL_GBYTES":{"type":["number","null"]},"KEY_VALUE_STORE_LISTS":{"type":["number","null"]},"KEY_VALUE_STORE_READS":{"type":["number","null"]},"KEY_VALUE_STORE_WRITES":{"type":["number","null"]},"PROXY_RESIDENTIAL_TRANSFER_GBYTES":{"type":["number","null"]},"PROXY_SERPS":{"type":["number","null"]},"PROXY_UNBLOCKER_UNITS":{"type":["number","null"]},"REQUEST_QUEUE_READS":{"type":["number","null"]},"REQUEST_QUEUE_WRITES":{"type":["number","null"]}},"type":"object"},"Schedule":{"allOf":[{"$ref":"#/components/schemas/ScheduleBase"},{"properties":{"actions":{"items":{"$ref":"#/components/schemas/ScheduleAction"},"type":"array"},"description":{"type":["string","null"]},"integrationTracking":{"$ref":"#/components/schemas/IntegrationTracking"},"notifications":{"$ref":"#/components/schemas/ScheduleNotifications"},"title":{"type":["string","null"]},"uiState":{"anyOf":[{"$ref":"#/components/schemas/ScheduleUiState"},{"type":"null"}]}},"required":["actions"],"type":"object"}]},"ScheduleAction":{"oneOf":[{"$ref":"#/components/schemas/ScheduleActionRunActor"},{"$ref":"#/components/schemas/ScheduleActionRunActorTask"}]},"ScheduleActionRunActor":{"properties":{"actorId":{"type":"string"},"id":{"type":"string"},"runInput":{"anyOf":[{"$ref":"#/components/schemas/ScheduleActionRunInput"},{"type":"null"}]},"runOptions":{"anyOf":[{"$ref":"#/components/schemas/TaskOptions"},{"type":"null"}]},"type":{}},"required":["id","type","actorId"],"type":"object"},"ScheduleActionRunActorTask":{"properties":{"actorTaskId":{"type":"string"},"id":{"type":"string"},"input":{"anyOf":[{"type":"object"},{"type":"null"}]},"type":{}},"required":["id","type","actorTaskId"],"type":"object"},"ScheduleActionRunInput":{"properties":{"body":{"type":["string","null"]},"contentType":{"type":["string","null"]}},"type":"object"},"ScheduleBase":{"properties":{"createdAt":{"format":"date-time","type":"string"},"cronExpression":{"type":"string"},"id":{"type":"string"},"isEnabled":{"type":"boolean"},"isExclusive":{"type":"boolean"},"lastRunAt":{"format":"date-time","type":["string","null"]},"modifiedAt":{"format":"date-time","type":"string"},"name":{"type":"string"},"nextRunAt":{"format":"date-time","type":["string","null"]},"timezone":{"type":"string"},"userId":{"type":"string"}},"required":["id","userId","name","cronExpression","timezone","isEnabled","isExclusive","createdAt","modifiedAt"],"type":"object"},"ScheduleListItem":{"allOf":[{"$ref":"#/components/schemas/ScheduleBase"},{"properties":{"actions":{"items":{"$ref":"#/components/schemas/ScheduleListItemAction"},"type":"array"},"integrationTracking":{"$ref":"#/components/schemas/IntegrationTracking"},"notifications":{"$ref":"#/components/schemas/ScheduleNotifications"},"title":{"type":["string","null"]}},"required":["actions"],"type":"object"}]},"ScheduleListItemAction":{"oneOf":[{"$ref":"#/components/schemas/ScheduleListItemActionRunActor"},{"$ref":"#/components/schemas/ScheduleListItemActionRunActorTask"}]},"ScheduleListItemActionRunActor":{"properties":{"actorId":{"type":"string"},"id":{"type":"string"},"type":{}},"required":["id","type","actorId"],"type":"object"},"ScheduleListItemActionRunActorTask":{"properties":{"actorTaskId":{"type":"string"},"id":{"type":"string"},"type":{}},"required":["id","type","actorTaskId"],"type":"object"},"ScheduleLogEntry":{"properties":{"createdAt":{"format":"date-time","type":"string"},"level":{"type":"string"},"message":{"type":"string"}},"required":["message","level","createdAt"],"type":"object"},"ScheduleLogResponse":{"properties":{"data":{"items":{"$ref":"#/components/schemas/ScheduleLogEntry"},"type":"array"}},"required":["data"],"type":"object"},"ScheduleNotifications":{"properties":{"email":{"type":"boolean"}},"type":"object"},"ScheduleResponse":{"properties":{"data":{"$ref":"#/components/schemas/Schedule"}},"required":["data"],"type":"object"},"ScheduleUiState":{"properties":{"data":{"properties":{"cronExpression":{"type":"string"},"timezone":{"type":"string"}},"type":"object"},"version":{"type":"integer"}},"type":"object"},"ServiceUsage":{"type":"object"},"SourceCodeFile":{"properties":{"content":{"type":"string"},"format":{"$ref":"#/components/schemas/SourceCodeFileFormat"},"name":{"type":"string"}},"required":["name"],"type":"object"},"SourceCodeFileFormat":{"type":"string"},"SourceCodeFolder":{"properties":{"folder":{"type":"boolean"},"name":{"type":"string"}},"required":["name","folder"],"type":"object"},"StoreActor":{"properties":{"actorReviewCount":{"type":"integer"},"actorReviewRating":{"type":"number"},"badge":{"type":["string","null"]},"bookmarkCount":{"type":"integer"},"categories":{"items":{"type":"string"},"type":"array"},"currentPricingInfo":{"$ref":"#/components/schemas/CurrentPricingInfo"},"description":{"type":["string","null"]},"id":{"type":"string"},"inputSchema":{"properties":{"properties":{"type":"object"},"required":{"items":{"type":"string"},"type":"array"},"type":{"type":"string"}},"required":["type","properties"],"type":"object"},"isWhiteListedForAgenticPayments":{"type":["boolean","null"]},"name":{"type":"string"},"notice":{"$ref":"#/components/schemas/ActorNotice"},"pictureUrl":{"type":["string","null"]},"readmeSummary":{"type":"string"},"stats":{"$ref":"#/components/schemas/ActorStats"},"title":{"type":"string"},"url":{"format":"uri","type":["string","null"]},"userFullName":{"type":["string","null"]},"userPictureUrl":{"format":"uri","type":["string","null"]},"username":{"type":"string"}},"required":["id","title","name","username","stats"],"type":"object"},"TaggedBuilds":{"type":"object"},"Task":{"properties":{"actId":{"type":"string"},"actorStandby":{"anyOf":[{"$ref":"#/components/schemas/ActorStandby"},{"type":"null"}]},"createdAt":{"format":"date-time","type":"string"},"description":{"type":["string","null"]},"id":{"type":"string"},"input":{"anyOf":[{"$ref":"#/components/schemas/TaskInput"},{"items":{"$ref":"#/components/schemas/TaskInput"},"type":"array"},{"type":"null"}]},"isPublic":{"type":"boolean"},"modifiedAt":{"format":"date-time","type":"string"},"name":{"type":"string"},"nameLowerCase":{"type":"string"},"options":{"anyOf":[{"$ref":"#/components/schemas/TaskOptions"},{"type":"null"}]},"publicConfig":{"anyOf":[{"$ref":"#/components/schemas/TaskPublicConfig"},{"type":"null"}]},"removedAt":{"format":"date-time","type":["string","null"]},"standbyUrl":{"format":"uri","type":["string","null"]},"stats":{"anyOf":[{"$ref":"#/components/schemas/TaskStats"},{"type":"null"}]},"title":{"type":["string","null"]},"userId":{"type":"string"},"username":{"type":["string","null"]}},"required":["id","userId","actId","name","createdAt","modifiedAt"],"type":"object"},"TaskInput":{"type":"object"},"TaskListItem":{"properties":{"actId":{"type":"string"},"actName":{"type":["string","null"]},"actUsername":{"type":["string","null"]},"createdAt":{"format":"date-time","type":"string"},"id":{"type":"string"},"isPublic":{"type":"boolean"},"modifiedAt":{"format":"date-time","type":"string"},"name":{"type":"string"},"stats":{"anyOf":[{"$ref":"#/components/schemas/TaskStats"},{"type":"null"}]},"title":{"type":["string","null"]},"userId":{"type":"string"},"username":{"type":["string","null"]}},"required":["id","userId","actId","name","createdAt","modifiedAt"],"type":"object"},"TaskOptions":{"properties":{"build":{"type":["string","null"]},"forcePermissionLevel":{"anyOf":[{"$ref":"#/components/schemas/ActorPermissionLevel"},{"type":"null"}]},"isMaxTotalChargeUsdSetByUser":{"type":["boolean","null"]},"maxItems":{"type":["integer","null"]},"maxTotalChargeUsd":{"type":["number","null"]},"memoryMbytes":{"type":["integer","null"]},"restartOnError":{"type":["boolean","null"]},"timeoutSecs":{"type":["integer","null"]}},"type":"object"},"TaskPublicConfig":{"properties":{"categorization":{"type":["string","null"]},"datasetName":{"type":["string","null"]},"datasetView":{"type":["string","null"]},"faq":{"items":{"properties":{"answer":{"type":"string"},"question":{"type":"string"}},"required":["question","answer"],"type":"object"},"type":["array","null"]},"highlightedFields":{"items":{"type":"string"},"type":["array","null"]},"inputSchemaFields":{"items":{"type":"string"},"type":["array","null"]},"publishedAt":{"format":"date-time","type":["string","null"]},"seoDescription":{"maxLength":160,"type":["string","null"]},"seoTitle":{"maxLength":60,"type":["string","null"]}},"type":"object"},"TaskResponse":{"properties":{"data":{"$ref":"#/components/schemas/Task"}},"required":["data"],"type":"object"},"TaskStats":{"properties":{"lastRunStartedAt":{"format":"date-time","type":["string","null"]},"totalRuns":{"type":"integer"}},"type":"object"},"TieredPricingPerDatasetItem":{"type":"object"},"TotalRequestCount":{"minimum":0,"type":"integer"},"UniqueKey":{"type":"string"},"UnlockRequestsResponse":{"properties":{"data":{"$ref":"#/components/schemas/UnlockRequestsResult"}},"required":["data"],"type":"object"},"UnlockRequestsResult":{"properties":{"unlockedCount":{"type":"integer"}},"required":["unlockedCount"],"type":"object"},"UnprocessedRequest":{"properties":{"id":{"$ref":"#/components/schemas/RequestId"},"method":{"$ref":"#/components/schemas/RequestMethod"},"uniqueKey":{"$ref":"#/components/schemas/UniqueKey"},"url":{"$ref":"#/components/schemas/RequestUrl"}},"required":["uniqueKey","url"],"type":"object"},"UpdateRequestResponse":{"properties":{"data":{"$ref":"#/components/schemas/RequestRegistration"}},"required":["data"],"type":"object"},"UsageCycle":{"properties":{"endAt":{"format":"date-time","type":"string"},"startAt":{"format":"date-time","type":"string"}},"required":["startAt","endAt"],"type":"object"},"UserPlan":{"properties":{"apiRateLimitBoosts":{"type":"integer"},"availableAddOns":{"items":{"type":"string"},"type":"array"},"availableProxyGroups":{"$ref":"#/components/schemas/AvailableProxyGroups"},"dataRetentionDays":{"type":"integer"},"description":{"type":"string"},"enabledPlatformFeatures":{"items":{"type":"string"},"type":"array"},"forceBillingInterval":{"type":"string"},"id":{"type":"string"},"isEnabled":{"type":"boolean"},"isPayingCustomer":{"type":"boolean"},"legacyPlanId":{"type":"string"},"maxActorCount":{"type":"integer"},"maxActorMemoryGbytes":{"type":"number"},"maxActorTaskCount":{"type":"integer"},"maxConcurrentActorRuns":{"type":"integer"},"maxMonthlyActorComputeUnits":{"type":"number"},"maxMonthlyExternalDataTransferGbytes":{"type":"number"},"maxMonthlyProxySerps":{"type":"integer"},"maxMonthlyResidentialProxyGbytes":{"type":"number"},"maxMonthlyUnblockerUnits":{"type":"integer"},"maxMonthlyUsageUsd":{"type":"number"},"maxPublicActorTaskCount":{"type":"integer"},"maxPublicActorTaskCountPerActor":{"type":"integer"},"maxScheduleCount":{"type":"integer"},"monitoringLevel":{"type":["string","null"]},"monthlyBasePriceUsd":{"type":"number"},"monthlyUsageCreditsUsd":{"type":"number"},"overageThresholdUsd":{"type":"number"},"personalizedSupport":{"type":["string","null"]},"planPricing":{"type":"object"},"planTags":{"items":{"type":"string"},"type":"array"},"planVersion":{"type":"number"},"supportLevel":{"type":"string"},"teamAccountSeatCount":{"type":"integer"},"tier":{"type":"string"},"usageDiscountPercent":{"type":"number"}},"required":["availableProxyGroups"],"type":"object"},"UserPrivateInfo":{"properties":{"createdAt":{"format":"date-time","type":"string"},"effectivePlatformFeatures":{"$ref":"#/components/schemas/EffectivePlatformFeatures"},"email":{"format":"email","type":["string","null"]},"id":{"type":"string"},"isPaying":{"type":"boolean"},"organizationOwnerUserId":{"type":["string","null"]},"plan":{"$ref":"#/components/schemas/UserPlan"},"profile":{"$ref":"#/components/schemas/UserProfile"},"proxy":{"$ref":"#/components/schemas/ProxyResource"},"username":{"type":"string"}},"required":["username"],"type":"object"},"UserPrivateInfoResponse":{"properties":{"data":{"$ref":"#/components/schemas/UserPrivateInfo"}},"required":["data"],"type":"object"},"UserProfile":{"properties":{"bio":{"type":["string","null"]},"githubUsername":{"type":["string","null"]},"name":{"type":"string"},"pictureUrl":{"format":"uri","type":["string","null"]},"readme":{"type":["string","null"]},"twitterUsername":{"type":["string","null"]},"websiteUrl":{"format":"uri","type":["string","null"]}},"type":"object"},"UserPublicInfo":{"properties":{"profile":{"$ref":"#/components/schemas/UserProfile"},"username":{"type":"string"}},"required":["username"],"type":"object"},"UserPublicInfoResponse":{"properties":{"data":{"$ref":"#/components/schemas/UserPublicInfo"}},"required":["data"],"type":"object"},"Version":{"properties":{"actorPath":{"maxLength":255,"minLength":1,"type":["string","null"]},"applyEnvVarsToBuild":{"type":["boolean","null"]},"bitbucketWebhookId":{"type":["string","null"]},"buildTag":{"type":["string","null"]},"envVars":{"anyOf":[{"items":{"$ref":"#/components/schemas/EnvVar"},"type":"array"},{"type":"null"}]},"gitHubGistUrl":{"type":["string","null"]},"gitRepoUrl":{"type":["string","null"]},"githubWebhookId":{"type":["integer","null"]},"gitlabWebhookId":{"type":["integer","null"]},"sourceFiles":{"$ref":"#/components/schemas/VersionSourceFiles"},"sourceType":{"anyOf":[{"$ref":"#/components/schemas/VersionSourceType"},{"type":"null"}]},"tarballUrl":{"type":["string","null"]},"versionNumber":{"pattern":"^([0-9]|[1-9][0-9])\\.([0-9]|[1-9][0-9])$","type":"string"}},"required":["versionNumber","sourceType"],"type":"object"},"VersionResponse":{"properties":{"data":{"$ref":"#/components/schemas/Version"}},"required":["data"],"type":"object"},"VersionSourceFiles":{"items":{"anyOf":[{"$ref":"#/components/schemas/SourceCodeFile"},{"$ref":"#/components/schemas/SourceCodeFolder"}]},"type":"array"},"VersionSourceType":{"type":"string"},"WasAlreadyHandled":{"type":"boolean"},"WasAlreadyPresent":{"type":"boolean"},"WebhookCondition":{"properties":{"actorId":{"type":["string","null"]},"actorRunId":{"type":["string","null"]},"actorTaskId":{"type":["string","null"]}},"type":"object"},"WebhookDatasetTransformation":{"properties":{"fields":{"items":{"type":"string"},"type":"array"},"omit":{"items":{"type":"string"},"type":"array"}},"type":"object"},"WebhookDispatch":{"properties":{"calls":{"items":{"properties":{"debugPostMessage":{"type":"object"},"errorMessage":{"type":["string","null"]},"finishedAt":{"format":"date-time","type":["string","null"]},"responseBody":{"type":["string","null"]},"responseStatus":{"type":["integer","null"]},"startedAt":{"format":"date-time","type":["string","null"]}},"type":"object"},"type":"array"},"createdAt":{"format":"date-time","type":"string"},"eventData":{"properties":{"actorBuildId":{"type":"string"},"actorId":{"type":"string"},"actorRunId":{"type":"string"},"actorTaskId":{"type":["string","null"]}},"required":["actorId"],"type":["object","null"]},"eventType":{"$ref":"#/components/schemas/WebhookEventType"},"id":{"type":"string"},"status":{"$ref":"#/components/schemas/WebhookDispatchStatus"},"userId":{"type":"string"},"webhook":{"anyOf":[{"$ref":"#/components/schemas/WebhookDispatchWebhookSummary"},{"type":"null"}]},"webhookId":{"type":"string"}},"required":["id","userId","webhookId","createdAt","status","eventType"],"type":"object"},"WebhookDispatchResponse":{"properties":{"data":{"$ref":"#/components/schemas/WebhookDispatch"}},"required":["data"],"type":"object"},"WebhookDispatchStatus":{"type":"string"},"WebhookDispatchWebhookSummary":{"properties":{"_id":{"type":"string"},"actionType":{"type":"string"},"condition":{"$ref":"#/components/schemas/WebhookCondition"},"githubIssue":{"$ref":"#/components/schemas/WebhookGithubIssue"},"googleDrive":{"$ref":"#/components/schemas/WebhookGoogleDrive"},"googleMail":{"$ref":"#/components/schemas/WebhookGoogleMail"},"isAdHoc":{"type":"boolean"},"isApifyIntegration":{"type":["boolean","null"]},"requestUrl":{"format":"uri","type":["string","null"]},"slackMessage":{"$ref":"#/components/schemas/WebhookSlackMessage"}},"type":"object"},"WebhookEventType":{"type":"string"},"WebhookGithubIssue":{"properties":{"body":{"type":"string"},"repository":{"type":"string"},"title":{"type":"string"},"userIntegrationId":{"type":"string"}},"type":"object"},"WebhookGoogleDrive":{"properties":{"filename":{"type":"string"},"format":{"type":["string","null"]},"transformation":{"$ref":"#/components/schemas/WebhookDatasetTransformation"},"userIntegrationId":{"type":"string"}},"type":"object"},"WebhookGoogleMail":{"properties":{"attachmentFormat":{"type":"string"},"attachmentTransformation":{"$ref":"#/components/schemas/WebhookDatasetTransformation"},"body":{"type":"string"},"emailTo":{"items":{"type":"string"},"type":"array"},"subject":{"type":"string"},"userIntegrationId":{"type":"string"}},"type":"object"},"WebhookLastDispatch":{"properties":{"finishedAt":{"format":"date-time","type":["string","null"]},"removedAt":{"format":"date-time","type":["string","null"]},"status":{"$ref":"#/components/schemas/WebhookDispatchStatus"}},"required":["status"],"type":"object"},"WebhookListItem":{"properties":{"actionType":{"type":"string"},"condition":{"$ref":"#/components/schemas/WebhookCondition"},"createdAt":{"format":"date-time","type":"string"},"description":{"type":["string","null"]},"doNotRetry":{"type":"boolean"},"eventTypes":{"items":{"$ref":"#/components/schemas/WebhookEventType"},"type":"array"},"githubIssue":{"$ref":"#/components/schemas/WebhookGithubIssue"},"googleDrive":{"$ref":"#/components/schemas/WebhookGoogleDrive"},"googleMail":{"$ref":"#/components/schemas/WebhookGoogleMail"},"headersTemplate":{"type":["string","null"]},"id":{"type":"string"},"ignoreSslErrors":{"type":"boolean"},"isAdHoc":{"type":["boolean","null"]},"isApifyIntegration":{"type":"boolean"},"isEnabled":{"type":"boolean"},"lastDispatch":{"anyOf":[{"$ref":"#/components/schemas/WebhookLastDispatch"},{"type":"null"}]},"modifiedAt":{"format":"date-time","type":"string"},"requestUrl":{"format":"uri","type":["string","null"]},"shouldInterpolateStrings":{"type":["boolean","null"]},"slackMessage":{"$ref":"#/components/schemas/WebhookSlackMessage"},"stats":{"anyOf":[{"$ref":"#/components/schemas/WebhookStats"},{"type":"null"}]},"title":{"type":["string","null"]},"userId":{"type":"string"}},"required":["id","createdAt","modifiedAt","userId","eventTypes","condition","ignoreSslErrors","doNotRetry"],"type":"object"},"WebhookResource":{"properties":{"actionType":{"type":"string"},"condition":{"$ref":"#/components/schemas/WebhookCondition"},"createdAt":{"format":"date-time","type":"string"},"description":{"type":["string","null"]},"doNotRetry":{"type":["boolean","null"]},"eventTypes":{"items":{"$ref":"#/components/schemas/WebhookEventType"},"type":"array"},"githubIssue":{"$ref":"#/components/schemas/WebhookGithubIssue"},"googleDrive":{"$ref":"#/components/schemas/WebhookGoogleDrive"},"googleMail":{"$ref":"#/components/schemas/WebhookGoogleMail"},"headersTemplate":{"type":["string","null"]},"id":{"type":"string"},"ignoreSslErrors":{"type":"boolean"},"isAdHoc":{"type":["boolean","null"]},"isApifyIntegration":{"type":["boolean","null"]},"isEnabled":{"type":"boolean"},"lastDispatch":{"anyOf":[{"$ref":"#/components/schemas/WebhookLastDispatch"},{"type":"null"}]},"modifiedAt":{"format":"date-time","type":"string"},"payloadTemplate":{"type":["string","null"]},"requestUrl":{"format":"uri","type":["string","null"]},"shouldInterpolateStrings":{"type":["boolean","null"]},"slackMessage":{"$ref":"#/components/schemas/WebhookSlackMessage"},"stats":{"anyOf":[{"$ref":"#/components/schemas/WebhookStats"},{"type":"null"}]},"title":{"type":["string","null"]},"userId":{"type":"string"}},"required":["id","createdAt","modifiedAt","userId","eventTypes","condition","ignoreSslErrors"],"type":"object"},"WebhookResponse":{"properties":{"data":{"$ref":"#/components/schemas/WebhookResource"}},"required":["data"],"type":"object"},"WebhookSlackMessage":{"properties":{"channelId":{"type":"string"},"messageTemplate":{"type":"string"},"userIntegrationId":{"type":"string"}},"type":"object"},"WebhookStats":{"properties":{"totalDispatches":{"type":"integer"}},"type":"object"}},"specVersion":"v2-2026-11-04T151257Z"} +{"operations":{"DELETE /v2/actor-runs/{runId}/request-queue/requests/batch":{"$ref":"#/components/schemas/BatchDeleteResponse"},"DELETE /v2/actor-tasks/{actorTaskId}":{"type":"object"},"DELETE /v2/actor-tasks/{actorTaskId}/runs/last/request-queue/requests/batch":{"$ref":"#/components/schemas/BatchDeleteResponse"},"DELETE /v2/actors/{actorId}":{"type":"object"},"DELETE /v2/actors/{actorId}/runs/last/request-queue/requests/batch":{"$ref":"#/components/schemas/BatchDeleteResponse"},"DELETE /v2/actors/{actorId}/versions/{versionNumber}":{"type":"object"},"DELETE /v2/actors/{actorId}/versions/{versionNumber}/env-vars/{envVarName}":{"type":"object"},"DELETE /v2/browser-info":{"$ref":"#/components/schemas/BrowserInfoResponse"},"DELETE /v2/request-queues/{queueId}/requests/batch":{"$ref":"#/components/schemas/BatchDeleteResponse"},"DELETE /v2/schedules/{scheduleId}":{"type":"object"},"DELETE /v2/webhooks/{webhookId}":{"type":"object"},"GET /v2/actor-builds":{"$ref":"#/components/schemas/ListOfBuildsResponse"},"GET /v2/actor-builds/{buildId}":{"allOf":[{"$ref":"#/components/schemas/BuildResponse"}]},"GET /v2/actor-builds/{buildId}/openapi.json":{"type":"object"},"GET /v2/actor-oauth-accounts/{accountId}":{"$ref":"#/components/schemas/ActorOAuthAccountResponse"},"GET /v2/actor-oauth-accounts/{connectionId}/accounts":{"$ref":"#/components/schemas/ListOfActorOAuthAccountsResponse"},"GET /v2/actor-runs":{"allOf":[{"$ref":"#/components/schemas/ListOfRunsResponse"}]},"GET /v2/actor-runs/{runId}":{"allOf":[{"$ref":"#/components/schemas/RunResponse"}]},"GET /v2/actor-runs/{runId}/dataset":{"$ref":"#/components/schemas/DatasetResponse"},"GET /v2/actor-runs/{runId}/dataset/items":{"items":{"type":"object"},"type":"array"},"GET /v2/actor-runs/{runId}/dataset/statistics":{"$ref":"#/components/schemas/DatasetStatisticsResponse"},"GET /v2/actor-runs/{runId}/key-value-store":{"$ref":"#/components/schemas/KeyValueStoreResponse"},"GET /v2/actor-runs/{runId}/key-value-store/keys":{"$ref":"#/components/schemas/ListOfKeysResponse"},"GET /v2/actor-runs/{runId}/key-value-store/records/{recordKey}":{"$ref":"#/components/schemas/RecordResponse"},"GET /v2/actor-runs/{runId}/request-queue":{"$ref":"#/components/schemas/RequestQueueResponse"},"GET /v2/actor-runs/{runId}/request-queue/head":{"$ref":"#/components/schemas/RequestQueueHeadResponse"},"GET /v2/actor-runs/{runId}/request-queue/requests":{"$ref":"#/components/schemas/ListOfRequestsResponse"},"GET /v2/actor-runs/{runId}/request-queue/requests/{requestId}":{"$ref":"#/components/schemas/RequestResponse"},"GET /v2/actor-tasks":{"$ref":"#/components/schemas/ListOfTasksResponse"},"GET /v2/actor-tasks/{actorTaskId}":{"properties":{"data":{"$ref":"#/components/schemas/Task"}},"required":["data"],"type":"object"},"GET /v2/actor-tasks/{actorTaskId}/input":{"type":"object"},"GET /v2/actor-tasks/{actorTaskId}/run-sync":{"type":"object"},"GET /v2/actor-tasks/{actorTaskId}/run-sync-get-dataset-items":{"type":"object"},"GET /v2/actor-tasks/{actorTaskId}/runs":{"properties":{"data":{"allOf":[{"$ref":"#/components/schemas/PaginationResponse"},{"properties":{"items":{"items":{"$ref":"#/components/schemas/RunListItem"},"type":"array"}},"required":["items"],"type":"object"}]}},"required":["data"],"type":"object"},"GET /v2/actor-tasks/{actorTaskId}/runs/last":{"properties":{"data":{"$ref":"#/components/schemas/Run"}},"required":["data"],"type":"object"},"GET /v2/actor-tasks/{actorTaskId}/runs/last/dataset":{"$ref":"#/components/schemas/DatasetResponse"},"GET /v2/actor-tasks/{actorTaskId}/runs/last/dataset/items":{"items":{"type":"object"},"type":"array"},"GET /v2/actor-tasks/{actorTaskId}/runs/last/dataset/statistics":{"$ref":"#/components/schemas/DatasetStatisticsResponse"},"GET /v2/actor-tasks/{actorTaskId}/runs/last/key-value-store":{"$ref":"#/components/schemas/KeyValueStoreResponse"},"GET /v2/actor-tasks/{actorTaskId}/runs/last/key-value-store/keys":{"$ref":"#/components/schemas/ListOfKeysResponse"},"GET /v2/actor-tasks/{actorTaskId}/runs/last/key-value-store/records/{recordKey}":{"$ref":"#/components/schemas/RecordResponse"},"GET /v2/actor-tasks/{actorTaskId}/runs/last/request-queue":{"$ref":"#/components/schemas/RequestQueueResponse"},"GET /v2/actor-tasks/{actorTaskId}/runs/last/request-queue/head":{"$ref":"#/components/schemas/RequestQueueHeadResponse"},"GET /v2/actor-tasks/{actorTaskId}/runs/last/request-queue/requests":{"$ref":"#/components/schemas/ListOfRequestsResponse"},"GET /v2/actor-tasks/{actorTaskId}/runs/last/request-queue/requests/{requestId}":{"$ref":"#/components/schemas/RequestResponse"},"GET /v2/actor-tasks/{actorTaskId}/webhooks":{"properties":{"data":{"allOf":[{"$ref":"#/components/schemas/PaginationResponse"},{"properties":{"items":{"items":{"$ref":"#/components/schemas/WebhookResource"},"type":"array"}},"required":["items"],"type":"object"}]}},"required":["data"],"type":"object"},"GET /v2/actors":{"$ref":"#/components/schemas/ListOfActorsResponse"},"GET /v2/actors/{actorId}":{"$ref":"#/components/schemas/ActorResponse"},"GET /v2/actors/{actorId}/builds":{"$ref":"#/components/schemas/ListOfBuildsResponse"},"GET /v2/actors/{actorId}/builds/default":{"$ref":"#/components/schemas/BuildResponse"},"GET /v2/actors/{actorId}/builds/{buildId}":{"$ref":"#/components/schemas/BuildResponse"},"GET /v2/actors/{actorId}/builds/{buildId}/openapi.json":{"type":"object"},"GET /v2/actors/{actorId}/oauth-connections":{"$ref":"#/components/schemas/ListOfActorOAuthConnectionsResponse"},"GET /v2/actors/{actorId}/run-sync":{"type":"object"},"GET /v2/actors/{actorId}/run-sync-get-dataset-items":{"items":{"type":"object"},"type":"array"},"GET /v2/actors/{actorId}/runs":{"$ref":"#/components/schemas/ListOfRunsResponse"},"GET /v2/actors/{actorId}/runs/last":{"$ref":"#/components/schemas/RunResponse"},"GET /v2/actors/{actorId}/runs/last/dataset":{"$ref":"#/components/schemas/DatasetResponse"},"GET /v2/actors/{actorId}/runs/last/dataset/items":{"items":{"type":"object"},"type":"array"},"GET /v2/actors/{actorId}/runs/last/dataset/statistics":{"$ref":"#/components/schemas/DatasetStatisticsResponse"},"GET /v2/actors/{actorId}/runs/last/key-value-store":{"$ref":"#/components/schemas/KeyValueStoreResponse"},"GET /v2/actors/{actorId}/runs/last/key-value-store/keys":{"$ref":"#/components/schemas/ListOfKeysResponse"},"GET /v2/actors/{actorId}/runs/last/key-value-store/records/{recordKey}":{"$ref":"#/components/schemas/RecordResponse"},"GET /v2/actors/{actorId}/runs/last/request-queue":{"$ref":"#/components/schemas/RequestQueueResponse"},"GET /v2/actors/{actorId}/runs/last/request-queue/head":{"$ref":"#/components/schemas/RequestQueueHeadResponse"},"GET /v2/actors/{actorId}/runs/last/request-queue/requests":{"$ref":"#/components/schemas/ListOfRequestsResponse"},"GET /v2/actors/{actorId}/runs/last/request-queue/requests/{requestId}":{"$ref":"#/components/schemas/RequestResponse"},"GET /v2/actors/{actorId}/runs/{runId}":{"$ref":"#/components/schemas/RunResponse"},"GET /v2/actors/{actorId}/versions":{"$ref":"#/components/schemas/ListOfVersionsResponse"},"GET /v2/actors/{actorId}/versions/{versionNumber}":{"$ref":"#/components/schemas/VersionResponse"},"GET /v2/actors/{actorId}/versions/{versionNumber}/env-vars":{"$ref":"#/components/schemas/ListOfEnvVarsResponse"},"GET /v2/actors/{actorId}/versions/{versionNumber}/env-vars/{envVarName}":{"$ref":"#/components/schemas/EnvVarResponse"},"GET /v2/actors/{actorId}/webhooks":{"$ref":"#/components/schemas/ListOfWebhookResourcesResponse"},"GET /v2/browser-info":{"$ref":"#/components/schemas/BrowserInfoResponse"},"GET /v2/datasets":{"$ref":"#/components/schemas/ListOfDatasetsResponse"},"GET /v2/datasets/{datasetId}":{"$ref":"#/components/schemas/DatasetResponse"},"GET /v2/datasets/{datasetId}/items":{"items":{"type":"object"},"type":"array"},"GET /v2/datasets/{datasetId}/statistics":{"$ref":"#/components/schemas/DatasetStatisticsResponse"},"GET /v2/integrations/git":{"$ref":"#/components/schemas/ListOfGitProviderIntegrationsResponse"},"GET /v2/key-value-stores":{"$ref":"#/components/schemas/ListOfKeyValueStoresResponse"},"GET /v2/key-value-stores/{storeId}":{"$ref":"#/components/schemas/KeyValueStoreResponse"},"GET /v2/key-value-stores/{storeId}/keys":{"$ref":"#/components/schemas/ListOfKeysResponse"},"GET /v2/key-value-stores/{storeId}/records/{recordKey}":{"$ref":"#/components/schemas/RecordResponse"},"GET /v2/request-queues":{"$ref":"#/components/schemas/ListOfRequestQueuesResponse"},"GET /v2/request-queues/{queueId}":{"$ref":"#/components/schemas/RequestQueueResponse"},"GET /v2/request-queues/{queueId}/head":{"$ref":"#/components/schemas/RequestQueueHeadResponse"},"GET /v2/request-queues/{queueId}/requests":{"$ref":"#/components/schemas/ListOfRequestsResponse"},"GET /v2/request-queues/{queueId}/requests/{requestId}":{"$ref":"#/components/schemas/RequestResponse"},"GET /v2/schedules":{"$ref":"#/components/schemas/ListOfSchedulesResponse"},"GET /v2/schedules/{scheduleId}":{"$ref":"#/components/schemas/ScheduleResponse"},"GET /v2/schedules/{scheduleId}/log":{"$ref":"#/components/schemas/ScheduleLogResponse"},"GET /v2/store":{"$ref":"#/components/schemas/ListOfStoreActorsResponse"},"GET /v2/users/me":{"$ref":"#/components/schemas/UserPrivateInfoResponse"},"GET /v2/users/me/limits":{"$ref":"#/components/schemas/AccountLimitsResponse"},"GET /v2/users/me/usage/monthly":{"$ref":"#/components/schemas/MonthlyUsageResponse"},"GET /v2/users/{userId}":{"anyOf":[{"$ref":"#/components/schemas/UserPublicInfoResponse"},{"$ref":"#/components/schemas/UserPrivateInfoResponse"}]},"GET /v2/users/{userId}/limits":{"$ref":"#/components/schemas/AccountLimitsResponse"},"GET /v2/users/{userId}/usage/monthly":{"$ref":"#/components/schemas/MonthlyUsageResponse"},"GET /v2/webhook-dispatches":{"$ref":"#/components/schemas/ListOfWebhookDispatchesResponse"},"GET /v2/webhook-dispatches/{dispatchId}":{"$ref":"#/components/schemas/WebhookDispatchResponse"},"GET /v2/webhooks":{"$ref":"#/components/schemas/ListOfWebhooksResponse"},"GET /v2/webhooks/{webhookId}":{"$ref":"#/components/schemas/WebhookResponse"},"GET /v2/webhooks/{webhookId}/dispatches":{"$ref":"#/components/schemas/ListOfWebhookDispatchesResponse"},"POST /v2/actor-builds/{buildId}/abort":{"allOf":[{"$ref":"#/components/schemas/BuildResponse"}]},"POST /v2/actor-runs/{runId}/abort":{"$ref":"#/components/schemas/RunResponse"},"POST /v2/actor-runs/{runId}/charge":{"type":"object"},"POST /v2/actor-runs/{runId}/dataset/items":{"type":"object"},"POST /v2/actor-runs/{runId}/key-value-store/records/{recordKey}":{"type":"object"},"POST /v2/actor-runs/{runId}/metamorph":{"$ref":"#/components/schemas/RunResponse"},"POST /v2/actor-runs/{runId}/reboot":{"$ref":"#/components/schemas/RunResponse"},"POST /v2/actor-runs/{runId}/request-queue/head/lock":{"$ref":"#/components/schemas/LockedRequestQueueHeadResponse"},"POST /v2/actor-runs/{runId}/request-queue/requests":{"$ref":"#/components/schemas/AddRequestResponse"},"POST /v2/actor-runs/{runId}/request-queue/requests/batch":{"$ref":"#/components/schemas/BatchAddResponse"},"POST /v2/actor-runs/{runId}/request-queue/requests/unlock":{"$ref":"#/components/schemas/UnlockRequestsResponse"},"POST /v2/actor-runs/{runId}/resurrect":{"allOf":[{"$ref":"#/components/schemas/RunResponse"}]},"POST /v2/actor-tasks":{"$ref":"#/components/schemas/TaskResponse"},"POST /v2/actor-tasks/{actorTaskId}/run-sync":{"type":"object"},"POST /v2/actor-tasks/{actorTaskId}/run-sync-get-dataset-items":{"type":"object"},"POST /v2/actor-tasks/{actorTaskId}/runs":{"properties":{"data":{"$ref":"#/components/schemas/Run"}},"required":["data"],"type":"object"},"POST /v2/actor-tasks/{actorTaskId}/runs/last/abort":{"$ref":"#/components/schemas/RunResponse"},"POST /v2/actor-tasks/{actorTaskId}/runs/last/dataset/items":{"type":"object"},"POST /v2/actor-tasks/{actorTaskId}/runs/last/key-value-store/records/{recordKey}":{"type":"object"},"POST /v2/actor-tasks/{actorTaskId}/runs/last/metamorph":{"$ref":"#/components/schemas/RunResponse"},"POST /v2/actor-tasks/{actorTaskId}/runs/last/reboot":{"$ref":"#/components/schemas/RunResponse"},"POST /v2/actor-tasks/{actorTaskId}/runs/last/request-queue/head/lock":{"$ref":"#/components/schemas/LockedRequestQueueHeadResponse"},"POST /v2/actor-tasks/{actorTaskId}/runs/last/request-queue/requests":{"$ref":"#/components/schemas/AddRequestResponse"},"POST /v2/actor-tasks/{actorTaskId}/runs/last/request-queue/requests/batch":{"$ref":"#/components/schemas/BatchAddResponse"},"POST /v2/actor-tasks/{actorTaskId}/runs/last/request-queue/requests/unlock":{"$ref":"#/components/schemas/UnlockRequestsResponse"},"POST /v2/actors":{"$ref":"#/components/schemas/ActorResponse"},"POST /v2/actors/{actorId}/builds":{"$ref":"#/components/schemas/BuildResponse"},"POST /v2/actors/{actorId}/builds/{buildId}/abort":{"$ref":"#/components/schemas/BuildResponse"},"POST /v2/actors/{actorId}/run-sync":{"type":"object"},"POST /v2/actors/{actorId}/run-sync-get-dataset-items":{"items":{"type":"object"},"type":"array"},"POST /v2/actors/{actorId}/runs":{"$ref":"#/components/schemas/RunResponse"},"POST /v2/actors/{actorId}/runs/last/abort":{"$ref":"#/components/schemas/RunResponse"},"POST /v2/actors/{actorId}/runs/last/dataset/items":{"type":"object"},"POST /v2/actors/{actorId}/runs/last/key-value-store/records/{recordKey}":{"type":"object"},"POST /v2/actors/{actorId}/runs/last/metamorph":{"$ref":"#/components/schemas/RunResponse"},"POST /v2/actors/{actorId}/runs/last/reboot":{"$ref":"#/components/schemas/RunResponse"},"POST /v2/actors/{actorId}/runs/last/request-queue/head/lock":{"$ref":"#/components/schemas/LockedRequestQueueHeadResponse"},"POST /v2/actors/{actorId}/runs/last/request-queue/requests":{"$ref":"#/components/schemas/AddRequestResponse"},"POST /v2/actors/{actorId}/runs/last/request-queue/requests/batch":{"$ref":"#/components/schemas/BatchAddResponse"},"POST /v2/actors/{actorId}/runs/last/request-queue/requests/unlock":{"$ref":"#/components/schemas/UnlockRequestsResponse"},"POST /v2/actors/{actorId}/runs/{runId}/abort":{"$ref":"#/components/schemas/RunResponse"},"POST /v2/actors/{actorId}/runs/{runId}/metamorph":{"$ref":"#/components/schemas/RunResponse"},"POST /v2/actors/{actorId}/runs/{runId}/resurrect":{"allOf":[{"$ref":"#/components/schemas/RunResponse"}]},"POST /v2/actors/{actorId}/validate-input":{"properties":{"valid":{"type":"boolean"}},"required":["valid"],"type":"object"},"POST /v2/actors/{actorId}/versions":{"$ref":"#/components/schemas/VersionResponse"},"POST /v2/actors/{actorId}/versions/{versionNumber}":{"$ref":"#/components/schemas/VersionResponse"},"POST /v2/actors/{actorId}/versions/{versionNumber}/env-vars":{"$ref":"#/components/schemas/EnvVarResponse"},"POST /v2/actors/{actorId}/versions/{versionNumber}/env-vars/{envVarName}":{"$ref":"#/components/schemas/EnvVarResponse"},"POST /v2/browser-info":{"$ref":"#/components/schemas/BrowserInfoResponse"},"POST /v2/datasets":{"$ref":"#/components/schemas/DatasetResponse"},"POST /v2/datasets/{datasetId}/items":{"type":"object"},"POST /v2/integrations/git/{providerId}/create-repo":{"$ref":"#/components/schemas/GitRepoResponse"},"POST /v2/integrations/git/{providerId}/register-deployment-key":{"$ref":"#/components/schemas/RegisterDeploymentKeyResponse"},"POST /v2/key-value-stores":{"$ref":"#/components/schemas/KeyValueStoreResponse"},"POST /v2/key-value-stores/{storeId}/records/{recordKey}":{"type":"object"},"POST /v2/request-queues":{"$ref":"#/components/schemas/RequestQueueResponse"},"POST /v2/request-queues/{queueId}/head/lock":{"$ref":"#/components/schemas/LockedRequestQueueHeadResponse"},"POST /v2/request-queues/{queueId}/requests":{"$ref":"#/components/schemas/AddRequestResponse"},"POST /v2/request-queues/{queueId}/requests/batch":{"$ref":"#/components/schemas/BatchAddResponse"},"POST /v2/request-queues/{queueId}/requests/unlock":{"$ref":"#/components/schemas/UnlockRequestsResponse"},"POST /v2/schedules":{"$ref":"#/components/schemas/ScheduleResponse"},"POST /v2/schedules/{scheduleId}/invoke":{"type":"object"},"POST /v2/tools/decode-and-verify":{"$ref":"#/components/schemas/DecodeAndVerifyResponse"},"POST /v2/tools/encode-and-sign":{"$ref":"#/components/schemas/EncodeAndSignResponse"},"POST /v2/webhooks":{"$ref":"#/components/schemas/WebhookResponse"},"POST /v2/webhooks/{webhookId}/test":{"$ref":"#/components/schemas/WebhookDispatchResponse"},"PUT /v2/actor-runs/{runId}":{"$ref":"#/components/schemas/RunResponse"},"PUT /v2/actor-runs/{runId}/dataset":{"$ref":"#/components/schemas/DatasetResponse"},"PUT /v2/actor-runs/{runId}/key-value-store":{"$ref":"#/components/schemas/KeyValueStoreResponse"},"PUT /v2/actor-runs/{runId}/key-value-store/records/{recordKey}":{"type":"object"},"PUT /v2/actor-runs/{runId}/request-queue":{"$ref":"#/components/schemas/RequestQueueResponse"},"PUT /v2/actor-runs/{runId}/request-queue/requests/{requestId}":{"$ref":"#/components/schemas/UpdateRequestResponse"},"PUT /v2/actor-runs/{runId}/request-queue/requests/{requestId}/lock":{"$ref":"#/components/schemas/ProlongRequestLockResponse"},"PUT /v2/actor-tasks/{actorTaskId}":{"properties":{"data":{"$ref":"#/components/schemas/Task"}},"required":["data"],"type":"object"},"PUT /v2/actor-tasks/{actorTaskId}/input":{"type":"object"},"PUT /v2/actor-tasks/{actorTaskId}/runs/last/dataset":{"$ref":"#/components/schemas/DatasetResponse"},"PUT /v2/actor-tasks/{actorTaskId}/runs/last/key-value-store":{"$ref":"#/components/schemas/KeyValueStoreResponse"},"PUT /v2/actor-tasks/{actorTaskId}/runs/last/key-value-store/records/{recordKey}":{"type":"object"},"PUT /v2/actor-tasks/{actorTaskId}/runs/last/request-queue":{"$ref":"#/components/schemas/RequestQueueResponse"},"PUT /v2/actor-tasks/{actorTaskId}/runs/last/request-queue/requests/{requestId}":{"$ref":"#/components/schemas/UpdateRequestResponse"},"PUT /v2/actor-tasks/{actorTaskId}/runs/last/request-queue/requests/{requestId}/lock":{"$ref":"#/components/schemas/ProlongRequestLockResponse"},"PUT /v2/actors/{actorId}":{"$ref":"#/components/schemas/ActorResponse"},"PUT /v2/actors/{actorId}/runs/last/dataset":{"$ref":"#/components/schemas/DatasetResponse"},"PUT /v2/actors/{actorId}/runs/last/key-value-store":{"$ref":"#/components/schemas/KeyValueStoreResponse"},"PUT /v2/actors/{actorId}/runs/last/key-value-store/records/{recordKey}":{"type":"object"},"PUT /v2/actors/{actorId}/runs/last/request-queue":{"$ref":"#/components/schemas/RequestQueueResponse"},"PUT /v2/actors/{actorId}/runs/last/request-queue/requests/{requestId}":{"$ref":"#/components/schemas/UpdateRequestResponse"},"PUT /v2/actors/{actorId}/runs/last/request-queue/requests/{requestId}/lock":{"$ref":"#/components/schemas/ProlongRequestLockResponse"},"PUT /v2/actors/{actorId}/versions/{versionNumber}":{"$ref":"#/components/schemas/VersionResponse"},"PUT /v2/actors/{actorId}/versions/{versionNumber}/env-vars/{envVarName}":{"$ref":"#/components/schemas/EnvVarResponse"},"PUT /v2/browser-info":{"$ref":"#/components/schemas/BrowserInfoResponse"},"PUT /v2/datasets/{datasetId}":{"$ref":"#/components/schemas/DatasetResponse"},"PUT /v2/key-value-stores/{storeId}":{"$ref":"#/components/schemas/KeyValueStoreResponse"},"PUT /v2/key-value-stores/{storeId}/records/{recordKey}":{"type":"object"},"PUT /v2/request-queues/{queueId}":{"$ref":"#/components/schemas/RequestQueueResponse"},"PUT /v2/request-queues/{queueId}/requests/{requestId}":{"$ref":"#/components/schemas/UpdateRequestResponse"},"PUT /v2/request-queues/{queueId}/requests/{requestId}/lock":{"$ref":"#/components/schemas/ProlongRequestLockResponse"},"PUT /v2/schedules/{scheduleId}":{"$ref":"#/components/schemas/ScheduleResponse"},"PUT /v2/users/me/limits":{"type":"object"},"PUT /v2/users/{userId}/limits":{"type":"object"},"PUT /v2/webhooks/{webhookId}":{"$ref":"#/components/schemas/WebhookResponse"}},"schemas":{"AccountLimits":{"properties":{"current":{"$ref":"#/components/schemas/CurrentUsage"},"limits":{"$ref":"#/components/schemas/Limits"},"monthlyUsageCycle":{"$ref":"#/components/schemas/UsageCycle"}},"required":["monthlyUsageCycle","limits","current"],"type":"object"},"AccountLimitsResponse":{"properties":{"data":{"$ref":"#/components/schemas/AccountLimits"}},"required":["data"],"type":"object"},"ActorDefinition":{"properties":{"actorSpecification":{"type":"integer"},"buildTag":{"type":"string"},"changelog":{"type":["string","null"]},"defaultMemoryMbytes":{"oneOf":[{"type":"string"},{"type":"integer"}]},"description":{"type":"string"},"dockerContextDir":{"type":"string"},"dockerfile":{"type":"string"},"environmentVariables":{"type":"object"},"input":{"type":["object","null"]},"maxMemoryMbytes":{"minimum":128,"type":"integer"},"meta":{"$ref":"#/components/schemas/ActorDefinitionMeta"},"minMemoryMbytes":{"minimum":128,"type":"integer"},"name":{"type":"string"},"output":{"type":["object","null"]},"readme":{"type":["string","null"]},"storages":{"properties":{"dataset":{"type":"object"},"datasets":{"type":"object"},"keyValueStore":{"type":"object"}},"type":"object"},"title":{"type":"string"},"usesStandbyMode":{"type":"boolean"},"version":{"pattern":"^[0-9]+(\\.[0-9]+)+$","type":"string"},"webServerMcpPath":{"type":"string"},"webServerSchema":{"type":"object"}},"type":"object"},"ActorDefinitionMeta":{"properties":{"generatedBy":{"type":"string"},"templateId":{"type":"string"}},"type":"object"},"ActorJobStatus":{"type":"string"},"ActorListItem":{"properties":{"createdAt":{"format":"date-time","type":"string"},"id":{"type":"string"},"modifiedAt":{"format":"date-time","type":"string"},"name":{"type":"string"},"stats":{"anyOf":[{"$ref":"#/components/schemas/ActorStats"},{"type":"null"}]},"title":{"type":"string"},"username":{"type":"string"}},"required":["id","createdAt","modifiedAt","name","username"],"type":"object"},"ActorNotice":{"type":["string","null"]},"ActorOAuthAccount":{"properties":{"_id":{"type":"string"},"authorizedAt":{"format":"date-time","type":["string","null"]},"connectionId":{"type":"string"},"createdAt":{"format":"date-time","type":"string"},"data":{"type":"object"},"description":{"type":["string","null"]},"modifiedAt":{"format":"date-time","type":"string"},"userId":{"type":"string"}},"required":["_id","userId","connectionId","createdAt","modifiedAt","data"],"type":"object"},"ActorOAuthAccountResponse":{"properties":{"data":{"$ref":"#/components/schemas/ActorOAuthAccount"}},"required":["data"],"type":"object"},"ActorOAuthConnection":{"properties":{"_id":{"type":"string"},"actorId":{"type":"string"},"createdAt":{"format":"date-time","type":"string"},"credentials":{"properties":{"clientId":{"type":"string"},"imageUrl":{"type":"string"},"label":{"type":"string"},"scope":{"type":"string"}},"type":"object"},"modifiedAt":{"format":"date-time","type":"string"},"oAuthProviderType":{"type":"string"}},"required":["_id","actorId","oAuthProviderType","createdAt","modifiedAt"],"type":"object"},"ActorPermissionLevel":{"type":"string"},"ActorResource":{"properties":{"actorPermissionLevel":{"$ref":"#/components/schemas/ActorPermissionLevel"},"actorStandby":{"anyOf":[{"$ref":"#/components/schemas/ActorStandby"},{"type":"null"}]},"categories":{"items":{"type":"string"},"type":"array"},"createdAt":{"format":"date-time","type":"string"},"defaultRunOptions":{"$ref":"#/components/schemas/DefaultRunOptions"},"deploymentKey":{"type":"string"},"description":{"type":["string","null"]},"exampleRunInput":{"anyOf":[{"$ref":"#/components/schemas/ExampleRunInput"},{"type":"null"}]},"hasNoDataset":{"type":"boolean"},"id":{"type":"string"},"isCritical":{"type":"boolean"},"isDeprecated":{"type":["boolean","null"]},"isGeneric":{"type":"boolean"},"isPublic":{"type":"boolean"},"isSourceCodeHidden":{"type":"boolean"},"modifiedAt":{"format":"date-time","type":"string"},"name":{"type":"string"},"notice":{"$ref":"#/components/schemas/ActorNotice"},"pictureUrl":{"type":["string","null"]},"pricingInfos":{"items":{"$ref":"#/components/schemas/ActorRunPricingInfo"},"type":"array"},"readmeSummary":{"type":"string"},"restartOnError":{"type":["boolean","null"]},"seoDescription":{"type":["string","null"]},"seoTitle":{"type":["string","null"]},"standbyUrl":{"type":["string","null"]},"stats":{"$ref":"#/components/schemas/ActorStats"},"taggedBuilds":{"anyOf":[{"$ref":"#/components/schemas/TaggedBuilds"},{"type":"null"}]},"title":{"type":["string","null"]},"userId":{"type":"string"},"username":{"type":"string"},"versions":{"items":{"$ref":"#/components/schemas/Version"},"type":"array"}},"required":["id","userId","name","username","isPublic","createdAt","modifiedAt","stats","versions","defaultRunOptions"],"type":"object"},"ActorResponse":{"properties":{"data":{"$ref":"#/components/schemas/ActorResource"}},"required":["data"],"type":"object"},"ActorRunPricingInfo":{"oneOf":[{"$ref":"#/components/schemas/PayPerEventActorPricingInfo"},{"$ref":"#/components/schemas/PricePerDatasetItemActorPricingInfo"},{"$ref":"#/components/schemas/FlatPricePerMonthActorPricingInfo"},{"$ref":"#/components/schemas/FreeActorPricingInfo"}]},"ActorStandby":{"properties":{"build":{"type":["string","null"]},"desiredRequestsPerActorRun":{"type":["integer","null"]},"disableStandbyFieldsOverride":{"type":["boolean","null"]},"idleTimeoutSecs":{"type":["integer","null"]},"isConsoleAuthEnabled":{"type":["boolean","null"]},"isEnabled":{"type":["boolean","null"]},"isTokenlessEnabled":{"type":["boolean","null"]},"maxRequestsPerActorRun":{"type":["integer","null"]},"memoryMbytes":{"type":["integer","null"]},"shouldPassActorInput":{"type":["boolean","null"]},"tenancy":{"type":["string","null"]}},"type":"object"},"ActorStats":{"properties":{"actorReviewCount":{"type":"integer"},"actorReviewRating":{"type":"number"},"bookmarkCount":{"type":"integer"},"lastRunStartedAt":{"format":"date-time","type":"string"},"publicActorRunStats30Days":{"properties":{"ABORTED":{"type":"integer"},"FAILED":{"type":"integer"},"SUCCEEDED":{"type":"integer"},"TIMED-OUT":{"type":"integer"},"TOTAL":{"type":"integer"}},"type":"object"},"totalBuilds":{"type":"integer"},"totalMetamorphs":{"type":"integer"},"totalRuns":{"type":"integer"},"totalUsers":{"type":"integer"},"totalUsers30Days":{"type":"integer"},"totalUsers7Days":{"type":"integer"},"totalUsers90Days":{"type":"integer"}},"type":"object"},"AddRequestResponse":{"properties":{"data":{"$ref":"#/components/schemas/RequestRegistration"}},"required":["data"],"type":"object"},"AddedRequest":{"properties":{"requestId":{"$ref":"#/components/schemas/RequestId"},"uniqueKey":{"$ref":"#/components/schemas/UniqueKey"},"wasAlreadyHandled":{"$ref":"#/components/schemas/WasAlreadyHandled"},"wasAlreadyPresent":{"$ref":"#/components/schemas/WasAlreadyPresent"}},"required":["requestId","uniqueKey","wasAlreadyHandled","wasAlreadyPresent"],"type":"object"},"AvailableProxyGroups":{"type":"object"},"BatchAddResponse":{"properties":{"data":{"$ref":"#/components/schemas/BatchAddResult"}},"required":["data"],"type":"object"},"BatchAddResult":{"properties":{"processedRequests":{"items":{"$ref":"#/components/schemas/AddedRequest"},"type":"array"},"unprocessedRequests":{"items":{"$ref":"#/components/schemas/UnprocessedRequest"},"type":"array"}},"required":["processedRequests","unprocessedRequests"],"type":"object"},"BatchDeleteResponse":{"properties":{"data":{"$ref":"#/components/schemas/BatchDeleteResult"}},"required":["data"],"type":"object"},"BatchDeleteResult":{"properties":{"processedRequests":{"items":{"$ref":"#/components/schemas/DeletedRequest"},"type":"array"},"unprocessedRequests":{"items":{"$ref":"#/components/schemas/UnprocessedRequest"},"type":"array"}},"required":["processedRequests","unprocessedRequests"],"type":"object"},"BrowserInfoResponse":{"properties":{"bodyLength":{"type":"integer"},"clientIp":{"type":["string","null"]},"countryCode":{"type":["string","null"]},"headers":{"type":"object"},"method":{"type":"string"},"rawHeaders":{"items":{"type":"string"},"type":"array"}},"required":["method","clientIp","countryCode","bodyLength"],"type":"object"},"Build":{"properties":{"actId":{"type":"string"},"actVersion":{"properties":{"actorPath":{"type":["string","null"]},"buildTag":{"type":["string","null"]},"gitHubGistUrl":{"type":"string"},"gitRepoUrl":{"type":"string"},"sourceFiles":{"items":{"$ref":"#/components/schemas/SourceCodeFile"},"type":"array"},"sourceType":{"$ref":"#/components/schemas/VersionSourceType"},"tarballUrl":{"type":"string"},"versionNumber":{"pattern":"^([0-9]|[1-9][0-9])\\.([0-9]|[1-9][0-9])$","type":"string"}},"type":"object"},"actorDefinition":{"anyOf":[{"$ref":"#/components/schemas/ActorDefinition"},{"type":"null"}]},"buildNumber":{"pattern":"^([0-9]|[1-9][0-9])\\.([0-9]|[1-9][0-9])(\\.[1-9][0-9]{0,4})$","type":"string"},"changelog":{"type":["string","null"]},"createdByOrganizationMemberUserId":{"type":"string"},"exitCode":{"type":["integer","null"]},"finishedAt":{"format":"date-time","type":["string","null"]},"gitBranchName":{"type":"string"},"gitCommitId":{"type":"string"},"id":{"type":"string"},"imageDigest":{"type":["string","null"]},"inputSchema":{"type":["string","null"]},"meta":{"$ref":"#/components/schemas/BuildMeta"},"options":{"anyOf":[{"$ref":"#/components/schemas/BuildOptions"},{"type":"null"}]},"readme":{"type":["string","null"]},"startedAt":{"format":"date-time","type":"string"},"stats":{"anyOf":[{"$ref":"#/components/schemas/BuildStats"},{"type":"null"}]},"status":{"$ref":"#/components/schemas/ActorJobStatus"},"usage":{"anyOf":[{"$ref":"#/components/schemas/BuildUsage"},{"type":"null"}]},"usageTotalUsd":{"type":["number","null"]},"usageUsd":{"anyOf":[{"$ref":"#/components/schemas/BuildUsage"},{"type":"null"}]},"userId":{"type":"string"}},"required":["id","actId","userId","startedAt","status","meta","buildNumber"],"type":"object"},"BuildListItem":{"properties":{"actId":{"type":"string"},"buildNumber":{"pattern":"^([0-9]|[1-9][0-9])\\.([0-9]|[1-9][0-9])(\\.[1-9][0-9]{0,4})$","type":"string"},"buildNumberInt":{"type":"integer"},"finishedAt":{"format":"date-time","type":["string","null"]},"id":{"type":"string"},"meta":{"$ref":"#/components/schemas/BuildMeta"},"startedAt":{"format":"date-time","type":"string"},"status":{"$ref":"#/components/schemas/ActorJobStatus"},"usageTotalUsd":{"type":"number"},"userId":{"type":"string"}},"required":["id","status","startedAt","buildNumber"],"type":"object"},"BuildMeta":{"properties":{"actorRunId":{"type":"string"},"clientIp":{"type":["string"]},"githubActionsRunUrl":{"type":"string"},"origin":{"$ref":"#/components/schemas/RunOrigin"},"userAgent":{"type":["string"]},"webhookDispatchId":{"type":"string"}},"required":["origin"],"type":"object"},"BuildOptions":{"properties":{"betaPackages":{"type":["boolean","null"]},"debug":{"type":["boolean","null"]},"diskMbytes":{"type":["integer","null"]},"memoryMbytes":{"type":["integer","null"]},"tag":{"type":["string","null"]},"useCache":{"type":["boolean","null"]}},"type":"object"},"BuildResponse":{"properties":{"data":{"$ref":"#/components/schemas/Build"}},"required":["data"],"type":"object"},"BuildStats":{"properties":{"computeUnits":{"type":"number"},"durationMillis":{"type":"integer"},"imageSizeBytes":{"type":["integer","null"]},"runTimeSecs":{"type":"number"}},"type":"object"},"BuildUsage":{"properties":{"ACTOR_COMPUTE_UNITS":{"type":["number","null"]}},"type":"object"},"CommonActorPricingInfo":{"properties":{"apifyMarginPercentage":{"type":"number"},"createdAt":{"format":"date-time","type":"string"},"forceContainsSignificantPriceChange":{"type":"boolean"},"isPriceChangeNotificationSuppressed":{"type":"boolean"},"notifiedAboutChangeAt":{"format":"date-time","type":["string","null"]},"notifiedAboutFutureChangeAt":{"format":"date-time","type":["string","null"]},"notifiedAboutRentalToPpePriceChangeAt":{"format":"date-time","type":["string","null"]},"reasonForChange":{"type":["string","null"]},"startedAt":{"format":"date-time","type":"string"}},"required":["apifyMarginPercentage","createdAt","startedAt"],"type":"object"},"CurrentPricingInfo":{"properties":{"apifyMarginPercentage":{"type":"number"},"createdAt":{"format":"date-time","type":"string"},"forceContainsSignificantPriceChange":{"type":"boolean"},"isPPEPlatformUsagePaidByUser":{"type":"boolean"},"isPriceChangeNotificationSuppressed":{"type":"boolean"},"minimalMaxTotalChargeUsd":{"type":["number","null"]},"notifiedAboutChangeAt":{"format":"date-time","type":["string","null"]},"notifiedAboutFutureChangeAt":{"format":"date-time","type":["string","null"]},"notifiedAboutRentalToPpePriceChangeAt":{"format":"date-time","type":["string","null"]},"pricePerUnitUsd":{"type":"number"},"pricingModel":{"type":"string"},"pricingPerEvent":{"type":["object","null"]},"reasonForChange":{"type":["string","null"]},"startedAt":{"format":"date-time","type":"string"},"tieredPricing":{"type":"object"},"trialMinutes":{"type":"integer"},"unitName":{"type":"string"}},"required":["pricingModel"],"type":"object"},"CurrentUsage":{"properties":{"activeActorJobCount":{"type":"integer"},"actorCount":{"type":"integer"},"actorMemoryGbytes":{"type":"number"},"actorTaskCount":{"type":"integer"},"monthlyActorComputeUnits":{"type":"number"},"monthlyExternalDataTransferGbytes":{"type":"number"},"monthlyProxySerps":{"type":"integer"},"monthlyResidentialProxyGbytes":{"type":"number"},"monthlyUnblockerUnits":{"type":"number"},"monthlyUsageUsd":{"type":"number"},"scheduleCount":{"type":"integer"},"teamAccountSeatCount":{"type":"integer"}},"required":["monthlyUsageUsd","monthlyActorComputeUnits","monthlyExternalDataTransferGbytes","monthlyProxySerps","monthlyResidentialProxyGbytes","actorMemoryGbytes","actorCount","actorTaskCount","activeActorJobCount","teamAccountSeatCount"],"type":"object"},"DailyServiceUsage":{"properties":{"date":{"format":"date-time","type":"string"},"serviceUsage":{"$ref":"#/components/schemas/ServiceUsage"},"totalUsageCreditsUsd":{"type":"number"}},"required":["date","serviceUsage","totalUsageCreditsUsd"],"type":"object"},"DatasetListItem":{"properties":{"accessedAt":{"format":"date-time","type":"string"},"actId":{"type":["string","null"]},"actRunId":{"type":["string","null"]},"cleanItemCount":{"type":"integer"},"createdAt":{"format":"date-time","type":"string"},"generalAccess":{"anyOf":[{"$ref":"#/components/schemas/GeneralAccess"},{"type":"null"}]},"id":{"type":"string"},"itemCount":{"type":"integer"},"modifiedAt":{"format":"date-time","type":"string"},"name":{"type":["string","null"]},"stats":{"$ref":"#/components/schemas/DatasetStats"},"title":{"type":["string","null"]},"userId":{"type":"string"},"username":{"type":"string"}},"required":["id","userId","createdAt","modifiedAt","accessedAt","itemCount","cleanItemCount"],"type":"object"},"DatasetResource":{"properties":{"accessedAt":{"format":"date-time","type":"string"},"actId":{"type":["string","null"]},"actRunId":{"type":["string","null"]},"cleanItemCount":{"minimum":0,"type":"integer"},"consoleUrl":{"format":"uri","type":"string"},"createdAt":{"format":"date-time","type":"string"},"fields":{"items":{"type":"string"},"type":["array","null"]},"generalAccess":{"anyOf":[{"$ref":"#/components/schemas/GeneralAccess"},{"type":"null"}]},"id":{"type":"string"},"itemCount":{"minimum":0,"type":"integer"},"itemsPublicUrl":{"format":"uri","type":"string"},"modifiedAt":{"format":"date-time","type":"string"},"name":{"type":["string","null"]},"schema":{"type":["object","null"]},"stats":{"$ref":"#/components/schemas/DatasetStats"},"title":{"type":["string","null"]},"urlSigningSecretKey":{"type":["string","null"]},"userId":{"type":"string"}},"required":["id","userId","createdAt","modifiedAt","accessedAt","itemCount","cleanItemCount","consoleUrl"],"type":"object"},"DatasetResponse":{"properties":{"data":{"$ref":"#/components/schemas/DatasetResource"}},"required":["data"],"type":"object"},"DatasetStatistics":{"properties":{"fieldStatistics":{"type":["object","null"]}},"type":"object"},"DatasetStatisticsResponse":{"properties":{"data":{"$ref":"#/components/schemas/DatasetStatistics"}},"required":["data"],"type":"object"},"DatasetStats":{"properties":{"inflatedBytes":{"type":"integer"},"readCount":{"type":"integer"},"storageBytes":{"type":"integer"},"writeCount":{"type":"integer"}},"type":"object"},"DecodeAndVerifyResponse":{"properties":{"data":{"$ref":"#/components/schemas/DecodeAndVerifyResult"}},"required":["data"],"type":"object"},"DecodeAndVerifyResult":{"properties":{"decoded":{},"encodedByUserId":{"type":["string","null"]},"isVerifiedUser":{"type":"boolean"}},"required":["decoded","encodedByUserId","isVerifiedUser"],"type":"object"},"DefaultRunOptions":{"properties":{"build":{"type":"string"},"forcePermissionLevel":{"anyOf":[{"$ref":"#/components/schemas/ActorPermissionLevel"},{"type":"null"}]},"isMaxTotalChargeUsdSetByUser":{"type":["boolean","null"]},"maxItems":{"type":["integer","null"]},"maxTotalChargeUsd":{"type":["number","null"]},"memoryMbytes":{"type":"integer"},"restartOnError":{"type":["boolean","null"]},"timeoutSecs":{"type":"integer"}},"type":"object"},"DeletedRequest":{"anyOf":[{"$ref":"#/components/schemas/DeletedRequestById"},{"$ref":"#/components/schemas/DeletedRequestByUniqueKey"}]},"DeletedRequestById":{"properties":{"id":{"$ref":"#/components/schemas/RequestId"},"uniqueKey":{"$ref":"#/components/schemas/UniqueKey"}},"required":["id"],"type":"object"},"DeletedRequestByUniqueKey":{"properties":{"id":{"$ref":"#/components/schemas/RequestId"},"uniqueKey":{"$ref":"#/components/schemas/UniqueKey"}},"required":["uniqueKey"],"type":"object"},"EffectivePlatformFeature":{"properties":{"disabledReason":{"type":["string","null"]},"disabledReasonType":{"type":["string","null"]},"isEnabled":{"type":"boolean"},"isTrial":{"type":"boolean"},"trialExpirationAt":{"format":"date-time","type":["string","null"]}},"required":["isEnabled","disabledReason","disabledReasonType","isTrial","trialExpirationAt"],"type":"object"},"EffectivePlatformFeatures":{"properties":{"ACTORS":{"$ref":"#/components/schemas/EffectivePlatformFeature"},"ACTORS_PUBLIC_ALL":{"$ref":"#/components/schemas/EffectivePlatformFeature"},"ACTORS_PUBLIC_DEVELOPER":{"$ref":"#/components/schemas/EffectivePlatformFeature"},"PROXY":{"$ref":"#/components/schemas/EffectivePlatformFeature"},"PROXY_EXTERNAL_ACCESS":{"$ref":"#/components/schemas/EffectivePlatformFeature"},"PROXY_RESIDENTIAL":{"$ref":"#/components/schemas/EffectivePlatformFeature"},"PROXY_SERPS":{"$ref":"#/components/schemas/EffectivePlatformFeature"},"SCHEDULER":{"$ref":"#/components/schemas/EffectivePlatformFeature"},"STORAGE":{"$ref":"#/components/schemas/EffectivePlatformFeature"},"WEBHOOKS":{"$ref":"#/components/schemas/EffectivePlatformFeature"}},"required":["ACTORS","STORAGE","SCHEDULER","PROXY","PROXY_EXTERNAL_ACCESS","PROXY_RESIDENTIAL","PROXY_SERPS","WEBHOOKS","ACTORS_PUBLIC_ALL","ACTORS_PUBLIC_DEVELOPER"],"type":"object"},"EncodeAndSignResponse":{"properties":{"data":{"$ref":"#/components/schemas/EncodeAndSignResult"}},"required":["data"],"type":"object"},"EncodeAndSignResult":{"properties":{"encoded":{"type":"string"}},"required":["encoded"],"type":"object"},"EnvVar":{"properties":{"isSecret":{"type":["boolean","null"]},"name":{"type":"string"},"value":{"type":"string"},"valueHash":{"type":"string"}},"required":["name"],"type":"object"},"EnvVarResponse":{"properties":{"data":{"$ref":"#/components/schemas/EnvVar"}},"required":["data"],"type":"object"},"ExampleRunInput":{"properties":{"body":{"type":"string"},"contentType":{"type":"string"}},"type":"object"},"FlatPricePerMonthActorPricingInfo":{"allOf":[{"$ref":"#/components/schemas/CommonActorPricingInfo"},{"properties":{"pricePerUnitUsd":{"type":"number"},"pricingModel":{"type":"string"},"trialMinutes":{"type":"integer"}},"required":["pricingModel","pricePerUnitUsd","trialMinutes"],"type":"object"}]},"FreeActorPricingInfo":{"allOf":[{"$ref":"#/components/schemas/CommonActorPricingInfo"},{"properties":{"pricingModel":{"type":"string"}},"required":["pricingModel"],"type":"object"}]},"GeneralAccess":{"type":"string"},"GitProviderIntegration":{"properties":{"addWorkspaceUrl":{"type":"string"},"id":{"type":"string"},"provider":{"type":"string"},"workspaces":{"items":{"$ref":"#/components/schemas/GitProviderWorkspace"},"type":"array"}},"required":["id","provider","workspaces"],"type":"object"},"GitProviderWorkspace":{"properties":{"id":{"type":"string"},"label":{"type":"string"}},"required":["id","label"],"type":"object"},"GitRepo":{"properties":{"htmlUrl":{"type":"string"},"httpsUrl":{"type":"string"},"sshUrl":{"type":"string"}},"required":["sshUrl","httpsUrl","htmlUrl"],"type":"object"},"GitRepoResponse":{"properties":{"data":{"$ref":"#/components/schemas/GitRepo"}},"required":["data"],"type":"object"},"HadMultipleClients":{"type":"boolean"},"HandledRequestCount":{"minimum":0,"type":"integer"},"HeadLimit":{"type":"integer"},"HttpMethod":{"type":"string"},"IntegrationTracking":{"properties":{"appId":{"type":"string"},"isAiTool":{"type":"boolean"},"platform":{"type":"string"}},"type":"object"},"KeyValueStoreKey":{"properties":{"key":{"type":"string"},"recordPublicUrl":{"format":"uri","type":"string"},"size":{"type":"integer"}},"required":["key","size","recordPublicUrl"],"type":"object"},"KeyValueStoreListItem":{"properties":{"accessedAt":{"format":"date-time","type":"string"},"actId":{"type":["string","null"]},"actRunId":{"type":["string","null"]},"createdAt":{"format":"date-time","type":"string"},"generalAccess":{"anyOf":[{"$ref":"#/components/schemas/GeneralAccess"},{"type":"null"}]},"id":{"type":"string"},"modifiedAt":{"format":"date-time","type":"string"},"name":{"type":["string","null"]},"schema":{"type":["object","string","null"]},"stats":{"$ref":"#/components/schemas/KeyValueStoreListStats"},"title":{"type":["string","null"]},"userId":{"type":["string","null"]},"username":{"type":["string","null"]}},"required":["id","createdAt","modifiedAt","accessedAt"],"type":"object"},"KeyValueStoreListStats":{"properties":{"deleteCount":{"type":"integer"},"listCount":{"type":"integer"},"readCount":{"type":"integer"},"s3StorageBytes":{"type":"integer"},"writeCount":{"type":"integer"}},"type":"object"},"KeyValueStoreResource":{"properties":{"accessedAt":{"format":"date-time","type":"string"},"actId":{"type":["string","null"]},"actRunId":{"type":["string","null"]},"consoleUrl":{"format":"uri","type":"string"},"createdAt":{"format":"date-time","type":"string"},"generalAccess":{"anyOf":[{"$ref":"#/components/schemas/GeneralAccess"},{"type":"null"}]},"id":{"type":"string"},"keysPublicUrl":{"format":"uri","type":"string"},"modifiedAt":{"format":"date-time","type":"string"},"name":{"type":["string","null"]},"recordsPublicUrl":{"format":"uri","type":"string"},"schema":{"type":["object","null"]},"stats":{"$ref":"#/components/schemas/KeyValueStoreStats"},"title":{"type":["string","null"]},"urlSigningSecretKey":{"type":["string","null"]},"userId":{"type":["string","null"]},"username":{"type":["string","null"]}},"required":["id","createdAt","modifiedAt","accessedAt"],"type":"object"},"KeyValueStoreResponse":{"properties":{"data":{"$ref":"#/components/schemas/KeyValueStoreResource"}},"required":["data"],"type":"object"},"KeyValueStoreStats":{"properties":{"deleteCount":{"type":"integer"},"listCount":{"type":"integer"},"readCount":{"type":"integer"},"s3StorageBytes":{"type":"integer"},"storageBytes":{"type":"integer"},"writeCount":{"type":"integer"}},"type":"object"},"Limits":{"properties":{"dataRetentionDays":{"type":"integer"},"maxActorCount":{"type":"integer"},"maxActorMemoryGbytes":{"type":"number"},"maxActorTaskCount":{"type":"integer"},"maxConcurrentActorJobs":{"type":"integer"},"maxMonthlyActorComputeUnits":{"type":"number"},"maxMonthlyExternalDataTransferGbytes":{"type":"number"},"maxMonthlyProxySerps":{"type":"integer"},"maxMonthlyResidentialProxyGbytes":{"type":"number"},"maxMonthlyUnblockerUnits":{"type":"number"},"maxMonthlyUsageUsd":{"type":"number"},"maxScheduleCount":{"type":"integer"},"maxTeamAccountSeatCount":{"type":"integer"}},"required":["maxMonthlyUsageUsd","maxMonthlyActorComputeUnits","maxMonthlyExternalDataTransferGbytes","maxMonthlyProxySerps","maxMonthlyResidentialProxyGbytes","maxActorMemoryGbytes","maxActorCount","maxActorTaskCount","maxConcurrentActorJobs","maxTeamAccountSeatCount","dataRetentionDays"],"type":"object"},"ListOfActorOAuthAccounts":{"properties":{"items":{"items":{"$ref":"#/components/schemas/ActorOAuthAccount"},"type":"array"},"total":{"type":"integer"}},"required":["total","items"],"type":"object"},"ListOfActorOAuthAccountsResponse":{"properties":{"data":{"$ref":"#/components/schemas/ListOfActorOAuthAccounts"}},"required":["data"],"type":"object"},"ListOfActorOAuthConnections":{"properties":{"items":{"items":{"$ref":"#/components/schemas/ActorOAuthConnection"},"type":"array"},"total":{"type":"integer"}},"required":["total","items"],"type":"object"},"ListOfActorOAuthConnectionsResponse":{"properties":{"data":{"$ref":"#/components/schemas/ListOfActorOAuthConnections"}},"required":["data"],"type":"object"},"ListOfActors":{"allOf":[{"$ref":"#/components/schemas/PaginationResponse"},{"properties":{"items":{"items":{"$ref":"#/components/schemas/ActorListItem"},"type":"array"}},"required":["items"],"type":"object"}]},"ListOfActorsResponse":{"properties":{"data":{"$ref":"#/components/schemas/ListOfActors"}},"required":["data"],"type":"object"},"ListOfBuilds":{"allOf":[{"$ref":"#/components/schemas/PaginationResponse"},{"properties":{"items":{"items":{"$ref":"#/components/schemas/BuildListItem"},"type":"array"}},"required":["items"],"type":"object"}]},"ListOfBuildsResponse":{"properties":{"data":{"$ref":"#/components/schemas/ListOfBuilds"}},"required":["data"],"type":"object"},"ListOfDatasets":{"allOf":[{"$ref":"#/components/schemas/PaginationResponse"},{"properties":{"items":{"items":{"$ref":"#/components/schemas/DatasetListItem"},"type":"array"},"unnamed":{"type":"boolean"}},"required":["items"],"type":"object"}]},"ListOfDatasetsResponse":{"properties":{"data":{"$ref":"#/components/schemas/ListOfDatasets"}},"required":["data"],"type":"object"},"ListOfEnvVars":{"properties":{"items":{"items":{"$ref":"#/components/schemas/EnvVar"},"type":"array"},"total":{"type":"integer"}},"required":["total","items"],"type":"object"},"ListOfEnvVarsResponse":{"properties":{"data":{"$ref":"#/components/schemas/ListOfEnvVars"}},"required":["data"],"type":"object"},"ListOfGitProviderIntegrationsResponse":{"properties":{"data":{"items":{"$ref":"#/components/schemas/GitProviderIntegration"},"type":"array"}},"required":["data"],"type":"object"},"ListOfKeyValueStores":{"allOf":[{"$ref":"#/components/schemas/PaginationResponse"},{"properties":{"items":{"items":{"$ref":"#/components/schemas/KeyValueStoreListItem"},"type":"array"},"unnamed":{"type":"boolean"}},"required":["items"],"type":"object"}]},"ListOfKeyValueStoresResponse":{"properties":{"data":{"$ref":"#/components/schemas/ListOfKeyValueStores"}},"required":["data"],"type":"object"},"ListOfKeys":{"properties":{"count":{"type":"integer"},"exclusiveStartKey":{"type":["string","null"]},"isTruncated":{"type":"boolean"},"items":{"items":{"$ref":"#/components/schemas/KeyValueStoreKey"},"type":"array"},"limit":{"type":"integer"},"nextExclusiveStartKey":{"type":["string","null"]}},"required":["items","count","limit","isTruncated"],"type":"object"},"ListOfKeysResponse":{"properties":{"data":{"$ref":"#/components/schemas/ListOfKeys"}},"required":["data"],"type":"object"},"ListOfRequestQueues":{"allOf":[{"$ref":"#/components/schemas/PaginationResponse"},{"properties":{"items":{"items":{"$ref":"#/components/schemas/RequestQueueListItem"},"type":"array"},"unnamed":{"type":"boolean"}},"required":["items"],"type":"object"}]},"ListOfRequestQueuesResponse":{"properties":{"data":{"$ref":"#/components/schemas/ListOfRequestQueues"}},"required":["data"],"type":"object"},"ListOfRequests":{"properties":{"cursor":{"type":"string"},"exclusiveStartId":{"type":"string"},"items":{"items":{"$ref":"#/components/schemas/RequestResource"},"type":"array"},"limit":{"type":"integer"},"nextCursor":{"type":"string"}},"required":["items","limit"],"type":"object"},"ListOfRequestsResponse":{"properties":{"data":{"$ref":"#/components/schemas/ListOfRequests"}},"required":["data"],"type":"object"},"ListOfRuns":{"allOf":[{"$ref":"#/components/schemas/PaginationResponse"},{"properties":{"items":{"items":{"$ref":"#/components/schemas/RunListItem"},"type":"array"}},"required":["items"],"type":"object"}]},"ListOfRunsResponse":{"properties":{"data":{"$ref":"#/components/schemas/ListOfRuns"}},"required":["data"],"type":"object"},"ListOfSchedules":{"allOf":[{"$ref":"#/components/schemas/PaginationResponse"},{"properties":{"items":{"items":{"$ref":"#/components/schemas/ScheduleListItem"},"type":"array"}},"required":["items"],"type":"object"}]},"ListOfSchedulesResponse":{"properties":{"data":{"$ref":"#/components/schemas/ListOfSchedules"}},"required":["data"],"type":"object"},"ListOfStoreActors":{"allOf":[{"$ref":"#/components/schemas/PaginationResponse"},{"properties":{"items":{"items":{"$ref":"#/components/schemas/StoreActor"},"type":"array"}},"required":["items"],"type":"object"}]},"ListOfStoreActorsResponse":{"properties":{"data":{"$ref":"#/components/schemas/ListOfStoreActors"}},"required":["data"],"type":"object"},"ListOfTasks":{"allOf":[{"$ref":"#/components/schemas/PaginationResponse"},{"properties":{"items":{"items":{"$ref":"#/components/schemas/TaskListItem"},"type":"array"}},"required":["items"],"type":"object"}]},"ListOfTasksResponse":{"properties":{"data":{"$ref":"#/components/schemas/ListOfTasks"}},"required":["data"],"type":"object"},"ListOfVersions":{"properties":{"items":{"items":{"$ref":"#/components/schemas/Version"},"type":"array"},"total":{"type":"integer"}},"required":["total","items"],"type":"object"},"ListOfVersionsResponse":{"properties":{"data":{"$ref":"#/components/schemas/ListOfVersions"}},"required":["data"],"type":"object"},"ListOfWebhookDispatches":{"allOf":[{"$ref":"#/components/schemas/PaginationResponse"},{"properties":{"items":{"items":{"$ref":"#/components/schemas/WebhookDispatch"},"type":"array"}},"required":["items"],"type":"object"}]},"ListOfWebhookDispatchesResponse":{"properties":{"data":{"$ref":"#/components/schemas/ListOfWebhookDispatches"}},"required":["data"],"type":"object"},"ListOfWebhookResources":{"allOf":[{"$ref":"#/components/schemas/PaginationResponse"},{"properties":{"items":{"items":{"$ref":"#/components/schemas/WebhookResource"},"type":"array"}},"required":["items"],"type":"object"}]},"ListOfWebhookResourcesResponse":{"properties":{"data":{"$ref":"#/components/schemas/ListOfWebhookResources"}},"required":["data"],"type":"object"},"ListOfWebhooks":{"allOf":[{"$ref":"#/components/schemas/PaginationResponse"},{"properties":{"items":{"items":{"$ref":"#/components/schemas/WebhookListItem"},"type":"array"}},"required":["items"],"type":"object"}]},"ListOfWebhooksResponse":{"properties":{"data":{"$ref":"#/components/schemas/ListOfWebhooks"}},"required":["data"],"type":"object"},"LockExpiresAt":{"format":"date-time","type":"string"},"LockedRequestQueueHead":{"properties":{"clientKey":{"type":"string"},"hadMultipleClients":{"$ref":"#/components/schemas/HadMultipleClients"},"items":{"items":{"$ref":"#/components/schemas/LockedRequestQueueHeadItem"},"type":"array"},"limit":{"$ref":"#/components/schemas/HeadLimit"},"lockSecs":{"type":"integer"},"queueHasLockedRequests":{"type":"boolean"},"queueModifiedAt":{"$ref":"#/components/schemas/QueueModifiedAt"}},"required":["limit","queueModifiedAt","hadMultipleClients","lockSecs","items"],"type":"object"},"LockedRequestQueueHeadItem":{"properties":{"id":{"$ref":"#/components/schemas/RequestId"},"lockExpiresAt":{"$ref":"#/components/schemas/LockExpiresAt"},"method":{"$ref":"#/components/schemas/RequestMethod"},"retryCount":{"$ref":"#/components/schemas/RetryCount"},"uniqueKey":{"$ref":"#/components/schemas/UniqueKey"},"url":{"$ref":"#/components/schemas/RequestUrl"}},"required":["id","uniqueKey","url","lockExpiresAt"],"type":"object"},"LockedRequestQueueHeadResponse":{"properties":{"data":{"$ref":"#/components/schemas/LockedRequestQueueHead"}},"required":["data"],"type":"object"},"MonthlyServiceUsage":{"type":"object"},"MonthlyUsage":{"properties":{"dailyServiceUsages":{"items":{"$ref":"#/components/schemas/DailyServiceUsage"},"type":"array"},"monthlyServiceUsage":{"$ref":"#/components/schemas/MonthlyServiceUsage"},"totalUsageCreditsUsdAfterVolumeDiscount":{"type":"number"},"totalUsageCreditsUsdBeforeVolumeDiscount":{"type":"number"},"usageCycle":{"$ref":"#/components/schemas/UsageCycle"}},"required":["usageCycle","monthlyServiceUsage","dailyServiceUsages","totalUsageCreditsUsdBeforeVolumeDiscount","totalUsageCreditsUsdAfterVolumeDiscount"],"type":"object"},"MonthlyUsageResponse":{"properties":{"data":{"$ref":"#/components/schemas/MonthlyUsage"}},"required":["data"],"type":"object"},"PaginationResponse":{"properties":{"count":{"minimum":0,"type":"integer"},"desc":{"type":"boolean"},"limit":{"minimum":1,"type":"integer"},"offset":{"minimum":0,"type":"integer"},"total":{"minimum":0,"type":"integer"}},"required":["total","offset","limit","desc","count"],"type":"object"},"PayPerEventActorPricingInfo":{"allOf":[{"$ref":"#/components/schemas/CommonActorPricingInfo"},{"properties":{"isPPEPlatformUsagePaidByUser":{"type":["boolean","null"]},"minimalMaxTotalChargeUsd":{"type":["number","null"]},"pricingModel":{"type":"string"},"pricingPerEvent":{"properties":{"actorChargeEvents":{"type":"object"}},"type":"object"}},"required":["pricingModel","pricingPerEvent"],"type":"object"}]},"PendingRequestCount":{"type":"integer"},"PricePerDatasetItemActorPricingInfo":{"allOf":[{"$ref":"#/components/schemas/CommonActorPricingInfo"},{"properties":{"pricePerUnitUsd":{"type":"number"},"pricingModel":{"type":"string"},"tieredPricing":{"$ref":"#/components/schemas/TieredPricingPerDatasetItem"},"unitName":{"type":"string"}},"required":["pricingModel","unitName"],"type":"object"}]},"ProlongRequestLockResponse":{"properties":{"data":{"$ref":"#/components/schemas/RequestLockInfo"}},"required":["data"],"type":"object"},"ProxyGroup":{"properties":{"availableCount":{"type":"integer"},"description":{"type":["string","null"]},"id":{"type":"string"},"name":{"type":"string"}},"required":["name","description"],"type":"object"},"ProxyResource":{"properties":{"groups":{"items":{"$ref":"#/components/schemas/ProxyGroup"},"type":"array"},"password":{"type":["string","null"]}},"required":["groups"],"type":"object"},"QueueAccessedAt":{"format":"date-time","type":"string"},"QueueCreatedAt":{"format":"date-time","type":"string"},"QueueId":{"type":"string"},"QueueModifiedAt":{"format":"date-time","type":"string"},"QueueUserId":{"type":"string"},"RecordResponse":{"type":"object"},"RegisterDeploymentKeyResponse":{"properties":{"data":{"properties":{"repoName":{"type":"string"}},"required":["repoName"],"type":"object"}},"required":["data"],"type":"object"},"RequestBase":{"properties":{"errorMessages":{"items":{"type":"string"},"type":["array","null"]},"handledAt":{"format":"date-time","type":["string","null"]},"headers":{"type":["object","null"]},"loadedUrl":{"type":["string","null"]},"method":{"$ref":"#/components/schemas/RequestMethod"},"noRetry":{"type":["boolean","null"]},"payload":{"type":["string","object","null"]},"retryCount":{"$ref":"#/components/schemas/RetryCount"},"uniqueKey":{"$ref":"#/components/schemas/UniqueKey"},"url":{"$ref":"#/components/schemas/RequestUrl"},"userData":{"$ref":"#/components/schemas/RequestUserData"}},"required":["uniqueKey","url"],"type":"object"},"RequestId":{"type":"string"},"RequestLockInfo":{"properties":{"lockExpiresAt":{"$ref":"#/components/schemas/LockExpiresAt"}},"required":["lockExpiresAt"],"type":"object"},"RequestMethod":{"$ref":"#/components/schemas/HttpMethod"},"RequestQueueHead":{"properties":{"hadMultipleClients":{"$ref":"#/components/schemas/HadMultipleClients"},"items":{"items":{"$ref":"#/components/schemas/RequestQueueHeadItem"},"type":"array"},"limit":{"$ref":"#/components/schemas/HeadLimit"},"queueModifiedAt":{"$ref":"#/components/schemas/QueueModifiedAt"}},"required":["limit","queueModifiedAt","hadMultipleClients","items"],"type":"object"},"RequestQueueHeadItem":{"properties":{"id":{"$ref":"#/components/schemas/RequestId"},"method":{"$ref":"#/components/schemas/RequestMethod"},"retryCount":{"$ref":"#/components/schemas/RetryCount"},"uniqueKey":{"$ref":"#/components/schemas/UniqueKey"},"url":{"$ref":"#/components/schemas/RequestUrl"}},"required":["id","uniqueKey","url"],"type":"object"},"RequestQueueHeadResponse":{"properties":{"data":{"$ref":"#/components/schemas/RequestQueueHead"}},"required":["data"],"type":"object"},"RequestQueueListItem":{"properties":{"accessedAt":{"$ref":"#/components/schemas/QueueAccessedAt"},"actId":{"type":["string","null"]},"actRunId":{"type":["string","null"]},"createdAt":{"$ref":"#/components/schemas/QueueCreatedAt"},"expireAt":{"format":"date-time","type":"string"},"generalAccess":{"anyOf":[{"$ref":"#/components/schemas/GeneralAccess"},{"type":"null"}]},"hadMultipleClients":{"$ref":"#/components/schemas/HadMultipleClients"},"handledRequestCount":{"$ref":"#/components/schemas/HandledRequestCount"},"id":{"$ref":"#/components/schemas/QueueId"},"modifiedAt":{"$ref":"#/components/schemas/QueueModifiedAt"},"name":{"type":["string","null"]},"pendingRequestCount":{"$ref":"#/components/schemas/PendingRequestCount"},"stats":{"$ref":"#/components/schemas/RequestQueueListStats"},"title":{"type":["string","null"]},"totalRequestCount":{"$ref":"#/components/schemas/TotalRequestCount"},"userId":{"$ref":"#/components/schemas/QueueUserId"},"username":{"type":"string"}},"required":["id","userId","createdAt","modifiedAt","accessedAt","totalRequestCount","handledRequestCount","pendingRequestCount","hadMultipleClients"],"type":"object"},"RequestQueueListStats":{"properties":{"deleteCount":{"type":"integer"},"dynamodbStorageBytes":{"type":"integer"},"headItemReadCount":{"type":"integer"},"readCount":{"type":"integer"},"s3StorageBytes":{"type":"integer"},"writeCount":{"type":"integer"}},"type":"object"},"RequestQueueResource":{"properties":{"accessedAt":{"$ref":"#/components/schemas/QueueAccessedAt"},"actId":{"type":["string","null"]},"actRunId":{"type":["string","null"]},"consoleUrl":{"format":"uri","type":"string"},"createdAt":{"$ref":"#/components/schemas/QueueCreatedAt"},"generalAccess":{"anyOf":[{"$ref":"#/components/schemas/GeneralAccess"},{"type":"null"}]},"hadMultipleClients":{"$ref":"#/components/schemas/HadMultipleClients"},"handledRequestCount":{"$ref":"#/components/schemas/HandledRequestCount"},"id":{"$ref":"#/components/schemas/QueueId"},"modifiedAt":{"$ref":"#/components/schemas/QueueModifiedAt"},"name":{"type":["string","null"]},"pendingRequestCount":{"$ref":"#/components/schemas/PendingRequestCount"},"stats":{"$ref":"#/components/schemas/RequestQueueStats"},"title":{"type":["string","null"]},"totalRequestCount":{"$ref":"#/components/schemas/TotalRequestCount"},"userId":{"$ref":"#/components/schemas/QueueUserId"}},"required":["id","userId","createdAt","modifiedAt","accessedAt","totalRequestCount","handledRequestCount","pendingRequestCount","hadMultipleClients","consoleUrl"],"type":"object"},"RequestQueueResponse":{"properties":{"data":{"$ref":"#/components/schemas/RequestQueueResource"}},"required":["data"],"type":"object"},"RequestQueueStats":{"properties":{"deleteCount":{"type":"integer"},"headItemReadCount":{"type":"integer"},"readCount":{"type":"integer"},"storageBytes":{"type":"integer"},"writeCount":{"type":"integer"}},"type":"object"},"RequestRegistration":{"properties":{"requestId":{"$ref":"#/components/schemas/RequestId"},"wasAlreadyHandled":{"$ref":"#/components/schemas/WasAlreadyHandled"},"wasAlreadyPresent":{"$ref":"#/components/schemas/WasAlreadyPresent"}},"required":["requestId","wasAlreadyPresent","wasAlreadyHandled"],"type":"object"},"RequestResource":{"allOf":[{"$ref":"#/components/schemas/RequestBase","required":["id"]},{"properties":{"id":{"$ref":"#/components/schemas/RequestId"},"lockByClient":{"type":"string"},"lockExpiresAt":{"$ref":"#/components/schemas/LockExpiresAt"}}}]},"RequestResponse":{"properties":{"data":{"$ref":"#/components/schemas/RequestResource"}},"required":["data"],"type":"object"},"RequestUrl":{"type":"string"},"RequestUserData":{"type":"object"},"RetryCount":{"type":"integer"},"Run":{"properties":{"accountedChargedEventCounts":{"type":["object","null"]},"actId":{"type":"string"},"actorTaskId":{"type":["string","null"]},"buildId":{"type":"string"},"buildNumber":{"pattern":"^([0-9]|[1-9][0-9])\\.([0-9]|[1-9][0-9])(\\.[1-9][0-9]{0,4})$","type":["string","null"]},"chargedEventCounts":{"type":"object"},"consoleUrl":{"format":"uri","type":"string"},"containerUrl":{"format":"uri","type":"string"},"createdByOrganizationMemberUserId":{"type":"string"},"defaultDatasetId":{"type":"string"},"defaultKeyValueStoreId":{"type":"string"},"defaultRequestQueueId":{"type":"string"},"eventUsage":{"type":"object"},"exitCode":{"type":["integer","null"]},"externalChargedTotalUsd":{"type":"number"},"finishedAt":{"format":"date-time","type":["string","null"]},"generalAccess":{"anyOf":[{"$ref":"#/components/schemas/GeneralAccess"},{"type":"null"}]},"gitBranchName":{"type":["string","null"]},"id":{"type":"string"},"integrationTracking":{"$ref":"#/components/schemas/IntegrationTracking"},"isContainerServerReady":{"type":["boolean","null"]},"isStatusMessageTerminal":{"type":["boolean","null"]},"meta":{"$ref":"#/components/schemas/RunMeta"},"metamorphs":{"anyOf":[{"items":{"$ref":"#/components/schemas/RunMetamorphEvent"},"type":"array"},{"type":"null"}]},"options":{"$ref":"#/components/schemas/RunOptions"},"output":{"type":"object"},"platformUsageBillingModel":{"type":"string"},"pricingInfo":{"$ref":"#/components/schemas/ActorRunPricingInfo"},"standby":{"anyOf":[{"$ref":"#/components/schemas/RunStandby"},{"type":"null"}]},"startedAt":{"format":"date-time","type":"string"},"stats":{"$ref":"#/components/schemas/RunStats"},"status":{"$ref":"#/components/schemas/ActorJobStatus"},"statusMessage":{"type":["string","null"]},"storageIds":{"properties":{"datasets":{"properties":{"default":{"type":"string"}},"type":"object"},"keyValueStores":{"properties":{"default":{"type":"string"}},"type":"object"},"requestQueues":{"properties":{"default":{"type":"string"}},"type":"object"}},"type":"object"},"usage":{"anyOf":[{"$ref":"#/components/schemas/RunUsage"},{"type":"null"}]},"usageTotalUsd":{"type":["number","null"]},"usageUsd":{"anyOf":[{"$ref":"#/components/schemas/RunUsageUsd"},{"type":"null"}]},"userId":{"type":"string"}},"required":["id","actId","userId","startedAt","status","meta","stats","options","buildId","defaultKeyValueStoreId","defaultDatasetId","defaultRequestQueueId"],"type":"object"},"RunListItem":{"properties":{"actId":{"type":"string"},"actorTaskId":{"type":["string","null"]},"buildId":{"type":"string"},"buildNumber":{"pattern":"^([0-9]|[1-9][0-9])\\.([0-9]|[1-9][0-9])(\\.[1-9][0-9]{0,4})$","type":"string"},"buildNumberInt":{"type":"integer"},"defaultDatasetId":{"type":"string"},"defaultKeyValueStoreId":{"type":"string"},"defaultRequestQueueId":{"type":"string"},"finishedAt":{"format":"date-time","type":["string","null"]},"id":{"type":"string"},"meta":{"$ref":"#/components/schemas/RunMeta"},"standby":{"anyOf":[{"$ref":"#/components/schemas/RunStandby"},{"type":"null"}]},"startedAt":{"format":"date-time","type":"string"},"status":{"$ref":"#/components/schemas/ActorJobStatus"},"usageTotalUsd":{"type":"number"},"userId":{"type":"string"}},"required":["id","actId","status","startedAt","buildId","meta","usageTotalUsd","defaultKeyValueStoreId","defaultDatasetId","defaultRequestQueueId"],"type":"object"},"RunMeta":{"properties":{"actorRunId":{"type":"string"},"clientIp":{"type":["string","null"]},"origin":{"$ref":"#/components/schemas/RunOrigin"},"scheduleId":{"type":["string","null"]},"scheduledAct2Id":{"type":"string"},"scheduledActorTaskId":{"type":"string"},"scheduledAt":{"format":"date-time","type":["string","null"]},"userAgent":{"type":["string","null"]},"webhookDispatchId":{"type":"string"}},"required":["origin"],"type":"object"},"RunMetamorphEvent":{"properties":{"actorId":{"type":"string"},"buildId":{"type":"string"},"buildTags":{"items":{"type":"string"},"type":"array"},"createdAt":{"format":"date-time","type":"string"},"inputKey":{"type":["string","null"]}},"required":["createdAt","actorId","buildId"],"type":"object"},"RunOptions":{"properties":{"build":{"type":"string"},"diskMbytes":{"minimum":0,"type":"integer"},"isMaxTotalChargeUsdSetByUser":{"type":"boolean"},"maxItems":{"minimum":0,"type":["integer","null"]},"maxTotalChargeUsd":{"minimum":0,"type":["number","null"]},"memoryMbytes":{"maximum":32768,"minimum":128,"type":"integer"},"restartOnError":{"type":["boolean","null"]},"timeoutSecs":{"minimum":0,"type":"integer"}},"required":["build","timeoutSecs","memoryMbytes","diskMbytes"],"type":"object"},"RunOrigin":{"type":"string"},"RunResponse":{"properties":{"data":{"$ref":"#/components/schemas/Run"}},"required":["data"],"type":"object"},"RunStandby":{"properties":{"deployment":{"type":"string"}},"required":["deployment"],"type":"object"},"RunStats":{"properties":{"computeUnits":{"minimum":0,"type":"number"},"cpuAvgUsage":{"type":"number"},"cpuCurrentUsage":{"type":"number"},"cpuMaxUsage":{"type":"number"},"durationMillis":{"minimum":0,"type":"integer"},"inputBodyLen":{"minimum":0,"type":["integer","null"]},"memAvgBytes":{"type":"number"},"memCurrentBytes":{"minimum":0,"type":"integer"},"memMaxBytes":{"minimum":0,"type":"integer"},"metamorph":{"minimum":0,"type":"integer"},"migrationCount":{"minimum":0,"type":"integer"},"netRxBytes":{"minimum":0,"type":"integer"},"netTxBytes":{"minimum":0,"type":"integer"},"rebootCount":{"minimum":0,"type":"integer"},"restartCount":{"minimum":0,"type":"integer"},"resurrectCount":{"minimum":0,"type":"integer"},"runTimeSecs":{"minimum":0,"type":"number"}},"type":"object"},"RunUsage":{"properties":{"ACTOR_COMPUTE_UNITS":{"type":["number","null"]},"DATASET_READS":{"type":["integer","null"]},"DATASET_WRITES":{"type":["integer","null"]},"DATA_TRANSFER_EXTERNAL_GBYTES":{"type":["number","null"]},"DATA_TRANSFER_INTERNAL_GBYTES":{"type":["number","null"]},"KEY_VALUE_STORE_LISTS":{"type":["integer","null"]},"KEY_VALUE_STORE_READS":{"type":["integer","null"]},"KEY_VALUE_STORE_WRITES":{"type":["integer","null"]},"PROXY_RESIDENTIAL_TRANSFER_GBYTES":{"type":["number","null"]},"PROXY_SERPS":{"type":["integer","null"]},"PROXY_UNBLOCKER_UNITS":{"type":["number","null"]},"REQUEST_QUEUE_READS":{"type":["integer","null"]},"REQUEST_QUEUE_WRITES":{"type":["integer","null"]}},"type":"object"},"RunUsageUsd":{"properties":{"ACTOR_COMPUTE_UNITS":{"type":["number","null"]},"DATASET_READS":{"type":["number","null"]},"DATASET_WRITES":{"type":["number","null"]},"DATA_TRANSFER_EXTERNAL_GBYTES":{"type":["number","null"]},"DATA_TRANSFER_INTERNAL_GBYTES":{"type":["number","null"]},"KEY_VALUE_STORE_LISTS":{"type":["number","null"]},"KEY_VALUE_STORE_READS":{"type":["number","null"]},"KEY_VALUE_STORE_WRITES":{"type":["number","null"]},"PROXY_RESIDENTIAL_TRANSFER_GBYTES":{"type":["number","null"]},"PROXY_SERPS":{"type":["number","null"]},"PROXY_UNBLOCKER_UNITS":{"type":["number","null"]},"REQUEST_QUEUE_READS":{"type":["number","null"]},"REQUEST_QUEUE_WRITES":{"type":["number","null"]}},"type":"object"},"Schedule":{"allOf":[{"$ref":"#/components/schemas/ScheduleBase"},{"properties":{"actions":{"items":{"$ref":"#/components/schemas/ScheduleAction"},"type":"array"},"description":{"type":["string","null"]},"integrationTracking":{"$ref":"#/components/schemas/IntegrationTracking"},"notifications":{"$ref":"#/components/schemas/ScheduleNotifications"},"title":{"type":["string","null"]},"uiState":{"anyOf":[{"$ref":"#/components/schemas/ScheduleUiState"},{"type":"null"}]}},"required":["actions"],"type":"object"}]},"ScheduleAction":{"oneOf":[{"$ref":"#/components/schemas/ScheduleActionRunActor"},{"$ref":"#/components/schemas/ScheduleActionRunActorTask"}]},"ScheduleActionRunActor":{"properties":{"actorId":{"type":"string"},"id":{"type":"string"},"runInput":{"anyOf":[{"$ref":"#/components/schemas/ScheduleActionRunInput"},{"type":"null"}]},"runOptions":{"anyOf":[{"$ref":"#/components/schemas/TaskOptions"},{"type":"null"}]},"type":{}},"required":["id","type","actorId"],"type":"object"},"ScheduleActionRunActorTask":{"properties":{"actorTaskId":{"type":"string"},"id":{"type":"string"},"input":{"anyOf":[{"type":"object"},{"type":"null"}]},"type":{}},"required":["id","type","actorTaskId"],"type":"object"},"ScheduleActionRunInput":{"properties":{"body":{"type":["string","null"]},"contentType":{"type":["string","null"]}},"type":"object"},"ScheduleBase":{"properties":{"createdAt":{"format":"date-time","type":"string"},"cronExpression":{"type":"string"},"id":{"type":"string"},"isEnabled":{"type":"boolean"},"isExclusive":{"type":"boolean"},"lastRunAt":{"format":"date-time","type":["string","null"]},"modifiedAt":{"format":"date-time","type":"string"},"name":{"type":"string"},"nextRunAt":{"format":"date-time","type":["string","null"]},"timezone":{"type":"string"},"userId":{"type":"string"}},"required":["id","userId","name","cronExpression","timezone","isEnabled","isExclusive","createdAt","modifiedAt"],"type":"object"},"ScheduleListItem":{"allOf":[{"$ref":"#/components/schemas/ScheduleBase"},{"properties":{"actions":{"items":{"$ref":"#/components/schemas/ScheduleListItemAction"},"type":"array"},"integrationTracking":{"$ref":"#/components/schemas/IntegrationTracking"},"notifications":{"$ref":"#/components/schemas/ScheduleNotifications"},"title":{"type":["string","null"]}},"required":["actions"],"type":"object"}]},"ScheduleListItemAction":{"oneOf":[{"$ref":"#/components/schemas/ScheduleListItemActionRunActor"},{"$ref":"#/components/schemas/ScheduleListItemActionRunActorTask"}]},"ScheduleListItemActionRunActor":{"properties":{"actorId":{"type":"string"},"id":{"type":"string"},"type":{}},"required":["id","type","actorId"],"type":"object"},"ScheduleListItemActionRunActorTask":{"properties":{"actorTaskId":{"type":"string"},"id":{"type":"string"},"type":{}},"required":["id","type","actorTaskId"],"type":"object"},"ScheduleLogEntry":{"properties":{"createdAt":{"format":"date-time","type":"string"},"level":{"type":"string"},"message":{"type":"string"}},"required":["message","level","createdAt"],"type":"object"},"ScheduleLogResponse":{"properties":{"data":{"items":{"$ref":"#/components/schemas/ScheduleLogEntry"},"type":"array"}},"required":["data"],"type":"object"},"ScheduleNotifications":{"properties":{"email":{"type":"boolean"}},"type":"object"},"ScheduleResponse":{"properties":{"data":{"$ref":"#/components/schemas/Schedule"}},"required":["data"],"type":"object"},"ScheduleUiState":{"properties":{"data":{"properties":{"cronExpression":{"type":"string"},"timezone":{"type":"string"}},"type":"object"},"version":{"type":"integer"}},"type":"object"},"ServiceUsage":{"type":"object"},"SourceCodeFile":{"properties":{"content":{"type":"string"},"format":{"$ref":"#/components/schemas/SourceCodeFileFormat"},"name":{"type":"string"}},"required":["name"],"type":"object"},"SourceCodeFileFormat":{"type":"string"},"SourceCodeFolder":{"properties":{"folder":{"type":"boolean"},"name":{"type":"string"}},"required":["name","folder"],"type":"object"},"StoreActor":{"properties":{"actorReviewCount":{"type":"integer"},"actorReviewRating":{"type":"number"},"badge":{"type":["string","null"]},"bookmarkCount":{"type":"integer"},"categories":{"items":{"type":"string"},"type":"array"},"currentPricingInfo":{"$ref":"#/components/schemas/CurrentPricingInfo"},"description":{"type":["string","null"]},"id":{"type":"string"},"inputSchema":{"properties":{"properties":{"type":"object"},"required":{"items":{"type":"string"},"type":"array"},"type":{"type":"string"}},"required":["type","properties"],"type":"object"},"isWhiteListedForAgenticPayments":{"type":["boolean","null"]},"name":{"type":"string"},"notice":{"$ref":"#/components/schemas/ActorNotice"},"pictureUrl":{"type":["string","null"]},"readmeSummary":{"type":"string"},"stats":{"$ref":"#/components/schemas/ActorStats"},"title":{"type":"string"},"url":{"format":"uri","type":["string","null"]},"userFullName":{"type":["string","null"]},"userPictureUrl":{"format":"uri","type":["string","null"]},"username":{"type":"string"}},"required":["id","title","name","username","stats"],"type":"object"},"TaggedBuilds":{"type":"object"},"Task":{"properties":{"actId":{"type":"string"},"actorStandby":{"anyOf":[{"$ref":"#/components/schemas/ActorStandby"},{"type":"null"}]},"createdAt":{"format":"date-time","type":"string"},"description":{"type":["string","null"]},"id":{"type":"string"},"input":{"anyOf":[{"$ref":"#/components/schemas/TaskInput"},{"items":{"$ref":"#/components/schemas/TaskInput"},"type":"array"},{"type":"null"}]},"isPublic":{"type":"boolean"},"modifiedAt":{"format":"date-time","type":"string"},"name":{"type":"string"},"nameLowerCase":{"type":"string"},"options":{"anyOf":[{"$ref":"#/components/schemas/TaskOptions"},{"type":"null"}]},"publicConfig":{"anyOf":[{"$ref":"#/components/schemas/TaskPublicConfig"},{"type":"null"}]},"removedAt":{"format":"date-time","type":["string","null"]},"standbyUrl":{"format":"uri","type":["string","null"]},"stats":{"anyOf":[{"$ref":"#/components/schemas/TaskStats"},{"type":"null"}]},"title":{"type":["string","null"]},"userId":{"type":"string"},"username":{"type":["string","null"]}},"required":["id","userId","actId","name","createdAt","modifiedAt"],"type":"object"},"TaskInput":{"type":"object"},"TaskListItem":{"properties":{"actId":{"type":"string"},"actName":{"type":["string","null"]},"actUsername":{"type":["string","null"]},"createdAt":{"format":"date-time","type":"string"},"id":{"type":"string"},"isPublic":{"type":"boolean"},"modifiedAt":{"format":"date-time","type":"string"},"name":{"type":"string"},"stats":{"anyOf":[{"$ref":"#/components/schemas/TaskStats"},{"type":"null"}]},"title":{"type":["string","null"]},"userId":{"type":"string"},"username":{"type":["string","null"]}},"required":["id","userId","actId","name","createdAt","modifiedAt"],"type":"object"},"TaskOptions":{"properties":{"build":{"type":["string","null"]},"forcePermissionLevel":{"anyOf":[{"$ref":"#/components/schemas/ActorPermissionLevel"},{"type":"null"}]},"isMaxTotalChargeUsdSetByUser":{"type":["boolean","null"]},"maxItems":{"type":["integer","null"]},"maxTotalChargeUsd":{"type":["number","null"]},"memoryMbytes":{"type":["integer","null"]},"restartOnError":{"type":["boolean","null"]},"timeoutSecs":{"type":["integer","null"]}},"type":"object"},"TaskPublicConfig":{"properties":{"categorization":{"type":["string","null"]},"datasetName":{"type":["string","null"]},"datasetView":{"type":["string","null"]},"faq":{"items":{"properties":{"answer":{"type":"string"},"question":{"type":"string"}},"required":["question","answer"],"type":"object"},"type":["array","null"]},"highlightedFields":{"items":{"type":"string"},"type":["array","null"]},"inputSchemaFields":{"items":{"type":"string"},"type":["array","null"]},"publishedAt":{"format":"date-time","type":["string","null"]},"seoDescription":{"maxLength":160,"type":["string","null"]},"seoTitle":{"maxLength":60,"type":["string","null"]}},"type":"object"},"TaskResponse":{"properties":{"data":{"$ref":"#/components/schemas/Task"}},"required":["data"],"type":"object"},"TaskStats":{"properties":{"lastRunStartedAt":{"format":"date-time","type":["string","null"]},"totalRuns":{"type":"integer"}},"type":"object"},"TieredPricingPerDatasetItem":{"type":"object"},"TotalRequestCount":{"minimum":0,"type":"integer"},"UniqueKey":{"type":"string"},"UnlockRequestsResponse":{"properties":{"data":{"$ref":"#/components/schemas/UnlockRequestsResult"}},"required":["data"],"type":"object"},"UnlockRequestsResult":{"properties":{"unlockedCount":{"type":"integer"}},"required":["unlockedCount"],"type":"object"},"UnprocessedRequest":{"properties":{"id":{"$ref":"#/components/schemas/RequestId"},"method":{"$ref":"#/components/schemas/RequestMethod"},"uniqueKey":{"$ref":"#/components/schemas/UniqueKey"},"url":{"$ref":"#/components/schemas/RequestUrl"}},"required":["uniqueKey","url"],"type":"object"},"UpdateRequestResponse":{"properties":{"data":{"$ref":"#/components/schemas/RequestRegistration"}},"required":["data"],"type":"object"},"UsageCycle":{"properties":{"endAt":{"format":"date-time","type":"string"},"startAt":{"format":"date-time","type":"string"}},"required":["startAt","endAt"],"type":"object"},"UserPlan":{"properties":{"apiRateLimitBoosts":{"type":"integer"},"availableAddOns":{"items":{"type":"string"},"type":"array"},"availableProxyGroups":{"$ref":"#/components/schemas/AvailableProxyGroups"},"dataRetentionDays":{"type":"integer"},"description":{"type":"string"},"enabledPlatformFeatures":{"items":{"type":"string"},"type":"array"},"forceBillingInterval":{"type":"string"},"id":{"type":"string"},"isEnabled":{"type":"boolean"},"isPayingCustomer":{"type":"boolean"},"legacyPlanId":{"type":"string"},"maxActorCount":{"type":"integer"},"maxActorMemoryGbytes":{"type":"number"},"maxActorTaskCount":{"type":"integer"},"maxConcurrentActorRuns":{"type":"integer"},"maxMonthlyActorComputeUnits":{"type":"number"},"maxMonthlyExternalDataTransferGbytes":{"type":"number"},"maxMonthlyProxySerps":{"type":"integer"},"maxMonthlyResidentialProxyGbytes":{"type":"number"},"maxMonthlyUnblockerUnits":{"type":"integer"},"maxMonthlyUsageUsd":{"type":"number"},"maxPublicActorTaskCount":{"type":"integer"},"maxPublicActorTaskCountPerActor":{"type":"integer"},"maxScheduleCount":{"type":"integer"},"monitoringLevel":{"type":["string","null"]},"monthlyBasePriceUsd":{"type":"number"},"monthlyUsageCreditsUsd":{"type":"number"},"overageThresholdUsd":{"type":"number"},"personalizedSupport":{"type":["string","null"]},"planPricing":{"type":"object"},"planTags":{"items":{"type":"string"},"type":"array"},"planVersion":{"type":"number"},"supportLevel":{"type":"string"},"teamAccountSeatCount":{"type":"integer"},"tier":{"type":"string"},"usageDiscountPercent":{"type":"number"}},"required":["availableProxyGroups"],"type":"object"},"UserPrivateInfo":{"properties":{"createdAt":{"format":"date-time","type":"string"},"effectivePlatformFeatures":{"$ref":"#/components/schemas/EffectivePlatformFeatures"},"email":{"format":"email","type":["string","null"]},"id":{"type":"string"},"isPaying":{"type":"boolean"},"organizationOwnerUserId":{"type":["string","null"]},"plan":{"$ref":"#/components/schemas/UserPlan"},"profile":{"$ref":"#/components/schemas/UserProfile"},"proxy":{"$ref":"#/components/schemas/ProxyResource"},"username":{"type":"string"}},"required":["username"],"type":"object"},"UserPrivateInfoResponse":{"properties":{"data":{"$ref":"#/components/schemas/UserPrivateInfo"}},"required":["data"],"type":"object"},"UserProfile":{"properties":{"bio":{"type":["string","null"]},"githubUsername":{"type":["string","null"]},"name":{"type":"string"},"pictureUrl":{"format":"uri","type":["string","null"]},"readme":{"type":["string","null"]},"twitterUsername":{"type":["string","null"]},"websiteUrl":{"format":"uri","type":["string","null"]}},"type":"object"},"UserPublicInfo":{"properties":{"profile":{"$ref":"#/components/schemas/UserProfile"},"username":{"type":"string"}},"required":["username"],"type":"object"},"UserPublicInfoResponse":{"properties":{"data":{"$ref":"#/components/schemas/UserPublicInfo"}},"required":["data"],"type":"object"},"Version":{"properties":{"actorPath":{"maxLength":255,"minLength":1,"type":["string","null"]},"applyEnvVarsToBuild":{"type":["boolean","null"]},"bitbucketWebhookId":{"type":["string","null"]},"buildTag":{"type":["string","null"]},"envVars":{"anyOf":[{"items":{"$ref":"#/components/schemas/EnvVar"},"type":"array"},{"type":"null"}]},"gitHubGistUrl":{"type":["string","null"]},"gitRepoUrl":{"type":["string","null"]},"githubWebhookId":{"type":["integer","null"]},"gitlabWebhookId":{"type":["integer","null"]},"sourceFiles":{"$ref":"#/components/schemas/VersionSourceFiles"},"sourceType":{"anyOf":[{"$ref":"#/components/schemas/VersionSourceType"},{"type":"null"}]},"tarballUrl":{"type":["string","null"]},"versionNumber":{"pattern":"^([0-9]|[1-9][0-9])\\.([0-9]|[1-9][0-9])$","type":"string"}},"required":["versionNumber","sourceType"],"type":"object"},"VersionResponse":{"properties":{"data":{"$ref":"#/components/schemas/Version"}},"required":["data"],"type":"object"},"VersionSourceFiles":{"items":{"anyOf":[{"$ref":"#/components/schemas/SourceCodeFile"},{"$ref":"#/components/schemas/SourceCodeFolder"}]},"type":"array"},"VersionSourceType":{"type":"string"},"WasAlreadyHandled":{"type":"boolean"},"WasAlreadyPresent":{"type":"boolean"},"WebhookCondition":{"properties":{"actorId":{"type":["string","null"]},"actorRunId":{"type":["string","null"]},"actorTaskId":{"type":["string","null"]}},"type":"object"},"WebhookDatasetTransformation":{"properties":{"fields":{"items":{"type":"string"},"type":"array"},"omit":{"items":{"type":"string"},"type":"array"}},"type":"object"},"WebhookDispatch":{"properties":{"calls":{"items":{"properties":{"debugPostMessage":{"type":"object"},"errorMessage":{"type":["string","null"]},"finishedAt":{"format":"date-time","type":["string","null"]},"responseBody":{"type":["string","null"]},"responseStatus":{"type":["integer","null"]},"startedAt":{"format":"date-time","type":["string","null"]}},"type":"object"},"type":"array"},"createdAt":{"format":"date-time","type":"string"},"eventData":{"properties":{"actorBuildId":{"type":"string"},"actorId":{"type":"string"},"actorRunId":{"type":"string"},"actorTaskId":{"type":["string","null"]}},"required":["actorId"],"type":["object","null"]},"eventType":{"$ref":"#/components/schemas/WebhookEventType"},"id":{"type":"string"},"status":{"$ref":"#/components/schemas/WebhookDispatchStatus"},"userId":{"type":"string"},"webhook":{"anyOf":[{"$ref":"#/components/schemas/WebhookDispatchWebhookSummary"},{"type":"null"}]},"webhookId":{"type":"string"}},"required":["id","userId","webhookId","createdAt","status","eventType"],"type":"object"},"WebhookDispatchResponse":{"properties":{"data":{"$ref":"#/components/schemas/WebhookDispatch"}},"required":["data"],"type":"object"},"WebhookDispatchStatus":{"type":"string"},"WebhookDispatchWebhookSummary":{"properties":{"_id":{"type":"string"},"actionType":{"type":"string"},"condition":{"$ref":"#/components/schemas/WebhookCondition"},"githubIssue":{"$ref":"#/components/schemas/WebhookGithubIssue"},"googleDrive":{"$ref":"#/components/schemas/WebhookGoogleDrive"},"googleMail":{"$ref":"#/components/schemas/WebhookGoogleMail"},"isAdHoc":{"type":"boolean"},"isApifyIntegration":{"type":["boolean","null"]},"requestUrl":{"format":"uri","type":["string","null"]},"slackMessage":{"$ref":"#/components/schemas/WebhookSlackMessage"}},"type":"object"},"WebhookEventType":{"type":"string"},"WebhookGithubIssue":{"properties":{"body":{"type":"string"},"repository":{"type":"string"},"title":{"type":"string"},"userIntegrationId":{"type":"string"}},"type":"object"},"WebhookGoogleDrive":{"properties":{"filename":{"type":"string"},"format":{"type":["string","null"]},"transformation":{"$ref":"#/components/schemas/WebhookDatasetTransformation"},"userIntegrationId":{"type":"string"}},"type":"object"},"WebhookGoogleMail":{"properties":{"attachmentFormat":{"type":"string"},"attachmentTransformation":{"$ref":"#/components/schemas/WebhookDatasetTransformation"},"body":{"type":"string"},"emailTo":{"items":{"type":"string"},"type":"array"},"subject":{"type":"string"},"userIntegrationId":{"type":"string"}},"type":"object"},"WebhookLastDispatch":{"properties":{"finishedAt":{"format":"date-time","type":["string","null"]},"removedAt":{"format":"date-time","type":["string","null"]},"status":{"$ref":"#/components/schemas/WebhookDispatchStatus"}},"required":["status"],"type":"object"},"WebhookListItem":{"properties":{"actionType":{"type":"string"},"condition":{"$ref":"#/components/schemas/WebhookCondition"},"createdAt":{"format":"date-time","type":"string"},"description":{"type":["string","null"]},"doNotRetry":{"type":"boolean"},"eventTypes":{"items":{"$ref":"#/components/schemas/WebhookEventType"},"type":"array"},"githubIssue":{"$ref":"#/components/schemas/WebhookGithubIssue"},"googleDrive":{"$ref":"#/components/schemas/WebhookGoogleDrive"},"googleMail":{"$ref":"#/components/schemas/WebhookGoogleMail"},"headersTemplate":{"type":["string","null"]},"id":{"type":"string"},"ignoreSslErrors":{"type":"boolean"},"isAdHoc":{"type":["boolean","null"]},"isApifyIntegration":{"type":"boolean"},"isEnabled":{"type":"boolean"},"lastDispatch":{"anyOf":[{"$ref":"#/components/schemas/WebhookLastDispatch"},{"type":"null"}]},"modifiedAt":{"format":"date-time","type":"string"},"requestUrl":{"format":"uri","type":["string","null"]},"shouldInterpolateStrings":{"type":["boolean","null"]},"slackMessage":{"$ref":"#/components/schemas/WebhookSlackMessage"},"stats":{"anyOf":[{"$ref":"#/components/schemas/WebhookStats"},{"type":"null"}]},"title":{"type":["string","null"]},"userId":{"type":"string"}},"required":["id","createdAt","modifiedAt","userId","eventTypes","condition","ignoreSslErrors","doNotRetry"],"type":"object"},"WebhookResource":{"properties":{"actionType":{"type":"string"},"condition":{"$ref":"#/components/schemas/WebhookCondition"},"createdAt":{"format":"date-time","type":"string"},"description":{"type":["string","null"]},"doNotRetry":{"type":["boolean","null"]},"eventTypes":{"items":{"$ref":"#/components/schemas/WebhookEventType"},"type":"array"},"githubIssue":{"$ref":"#/components/schemas/WebhookGithubIssue"},"googleDrive":{"$ref":"#/components/schemas/WebhookGoogleDrive"},"googleMail":{"$ref":"#/components/schemas/WebhookGoogleMail"},"headersTemplate":{"type":["string","null"]},"id":{"type":"string"},"ignoreSslErrors":{"type":"boolean"},"isAdHoc":{"type":["boolean","null"]},"isApifyIntegration":{"type":["boolean","null"]},"isEnabled":{"type":"boolean"},"lastDispatch":{"anyOf":[{"$ref":"#/components/schemas/WebhookLastDispatch"},{"type":"null"}]},"modifiedAt":{"format":"date-time","type":"string"},"payloadTemplate":{"type":["string","null"]},"requestUrl":{"format":"uri","type":["string","null"]},"shouldInterpolateStrings":{"type":["boolean","null"]},"slackMessage":{"$ref":"#/components/schemas/WebhookSlackMessage"},"stats":{"anyOf":[{"$ref":"#/components/schemas/WebhookStats"},{"type":"null"}]},"title":{"type":["string","null"]},"userId":{"type":"string"}},"required":["id","createdAt","modifiedAt","userId","eventTypes","condition","ignoreSslErrors"],"type":"object"},"WebhookResponse":{"properties":{"data":{"$ref":"#/components/schemas/WebhookResource"}},"required":["data"],"type":"object"},"WebhookSlackMessage":{"properties":{"channelId":{"type":"string"},"messageTemplate":{"type":"string"},"userIntegrationId":{"type":"string"}},"type":"object"},"WebhookStats":{"properties":{"totalDispatches":{"type":"integer"}},"type":"object"}},"specVersion":"v2-2026-11-09T143926Z"} diff --git a/src/Apify.Client/Internal/RetryConfig.cs b/src/Apify.Client/Internal/RetryConfig.cs index 0a78b1e..baa4d53 100644 --- a/src/Apify.Client/Internal/RetryConfig.cs +++ b/src/Apify.Client/Internal/RetryConfig.cs @@ -1,18 +1,13 @@ namespace Apify.Client.Internal; -/// -/// Retry/timeout policy for the orchestrating HTTP client. -/// +/// Retry and timeout policy of the HTTP client. internal sealed class RetryConfig { /// Creates a retry policy. - /// Maximum retries (the request is attempted up to maxRetries + 1 times). - /// Minimum delay between retries, in ms; doubled on each retry. - /// Upper bound on the (exponentially growing) inter-retry delay, in ms. - /// - /// Overall per-request timeout budget, in seconds: the cap every attempt's growing timeout is clamped - /// to (the reference client's timeoutMaxSecs). - /// + /// Maximum retries; a request is attempted up to maxRetries + 1 times. + /// Minimum delay between retries in ms, doubled on each retry. + /// Upper bound of the growing delay in ms. + /// Overall per-request timeout in seconds: the cap of every attempt's timeout (the reference client's timeoutMaxSecs). /// Base duration of the tier, in seconds. /// Base duration of the tier, in seconds. /// Base duration of the tier, in seconds. @@ -27,19 +22,16 @@ public RetryConfig(int maxRetries, double minDelayMillis, double maxDelayMillis, MediumTimeoutSecs = mediumTimeoutSecs; } - /// Maximum number of retries (the request is attempted up to MaxRetries + 1 times). + /// Maximum number of retries. public int MaxRetries { get; } - /// Minimum delay between retries, in milliseconds; doubled on each retry (exponential backoff). + /// Minimum delay between retries in ms, doubled on each retry. public double MinDelayMillis { get; } - /// Upper bound on the (exponentially growing) inter-retry delay, in milliseconds. + /// Upper bound of the growing delay between retries in ms. public double MaxDelayMillis { get; } - /// - /// Overall per-request timeout budget, in seconds (every attempt's growing timeout is capped here, - /// regardless of tier). - /// + /// Overall per-request timeout in seconds: the cap of every attempt's timeout, whatever the tier. public double TimeoutSecs { get; } /// Base duration of the tier, in seconds. diff --git a/src/Apify.Client/Internal/Signatures.cs b/src/Apify.Client/Internal/Signatures.cs index 0dc99f4..67d2bb5 100644 --- a/src/Apify.Client/Internal/Signatures.cs +++ b/src/Apify.Client/Internal/Signatures.cs @@ -6,28 +6,24 @@ namespace Apify.Client.Internal; -/// -/// Apify storage-content URL signing, byte-for-byte compatible with the platform's -/// @apify/utilities implementation that the reference clients rely on. -/// +/// Signing of storage-content URLs, byte-for-byte compatible with @apify/utilities. internal static class Signatures { - /// Version tag embedded in storage-content signatures (upstream default). + /// Version tag in the signatures. private const string StorageContentSignatureVersion = "0"; - /// Number of leading hex characters of the HMAC digest used. + /// Number of leading hex characters of the HMAC digest that are used. private const int HmacSignatureHexLen = 30; - /// Base62 alphabet (digits, then lowercase, then uppercase), matching upstream. + /// Base62 alphabet: digits, lowercase, uppercase. private const string Base62Alphabet = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; private const int Base = 62; private const int ByteBase = 256; /// - /// Computes an Apify URL-signing signature, byte-for-byte compatible with upstream - /// createHmacSignature: HMAC-SHA256(secret, message) as lowercase hex, take the first 30 hex - /// characters, interpret them as a big integer, then base62-encode (alphabet 0-9a-zA-Z). + /// Computes the signature as createHmacSignature upstream does: HMAC-SHA256 as lowercase hex, the first 30 hex + /// characters read as a big integer and base62-encoded. /// public static string CreateHmacSignature(string secretKey, string message) { @@ -38,9 +34,8 @@ public static string CreateHmacSignature(string secretKey, string message) } /// - /// Rejects a zero or negative expiry, which would yield a URL that has already expired (the expiry is added to - /// the current time); a caller that wants a permanent URL leaves the expiry out. Matches the reference - /// client, which requires a positive expiresInSecs. + /// Rejects a zero or negative expiry, which would give a URL that has already expired. Omit the expiry for a permanent + /// URL. The reference client also requires a positive expiresInSecs. /// /// The expiry from the caller's arguments, or null when unset. /// The caller's parameter name, used in the error message. @@ -52,18 +47,15 @@ public static void ValidateExpiry(int? expiresInSecs, string paramName) } } - /// - /// Builds a storage-content signature for a resource's public URL, byte-for-byte compatible with - /// upstream createStorageContentSignature. - /// + /// Builds the signature of a resource's public URL, as upstream createStorageContentSignature. /// - /// It signs the message "{version}.{expiresAtMillis}.{resourceId}" (expiresAtMillis is - /// the absolute expiry in ms, or 0 for a non-expiring URL) with , - /// then returns the base64url (no padding) encoding of "{version}.{expiresAtMillis}.{hmac}". + /// It signs "{version}.{expiresAtMillis}.{resourceId}" (expiresAtMillis is the absolute expiry, or + /// 0 for none) with and returns the base64url encoding, without padding, of + /// "{version}.{expiresAtMillis}.{hmac}". /// - /// The store/dataset URL-signing secret key. + /// The URL-signing secret key of the store or dataset. /// The resource id being signed. - /// Optional expiry in seconds (null for a non-expiring URL). + /// Optional expiry in seconds (null for none). public static string SignStorageContent(string secretKey, string resourceId, int? expiresInSecs) { ValidateExpiry(expiresInSecs, nameof(expiresInSecs)); @@ -78,10 +70,7 @@ public static string SignStorageContent(string secretKey, string resourceId, int return Base64UrlNoPadding(Encoding.UTF8.GetBytes(envelope)); } - /// - /// Interprets a hex string as a big-endian non-negative integer and encodes it in base62. - /// Implemented with byte-wise long division (base 256 → base 62) so it needs no bignum dependency. - /// + /// Encodes a hex string, read as a big-endian integer, in base62 by long division, without a bignum dependency. private static string HexToBase62(string hex) { var digits = new List(Convert.FromHexString(hex).Length); @@ -118,7 +107,7 @@ private static string HexToBase62(string hex) return result.Length == 0 ? "0" : result.ToString(); } - /// Encodes bytes as base64url without padding (+/ → -_, trailing = removed). + /// Encodes bytes as base64url without padding. private static string Base64UrlNoPadding(byte[] bytes) { return Convert.ToBase64String(bytes) diff --git a/src/Apify.Client/Models/Actor.cs b/src/Apify.Client/Models/Actor.cs index d8c02b3..09ad350 100644 --- a/src/Apify.Client/Models/Actor.cs +++ b/src/Apify.Client/Models/Actor.cs @@ -33,28 +33,21 @@ public Actor(JsonObject data) /// Whether the Actor is publicly available in Apify Store. public bool? IsPublic => GetBool("isPublic"); - /// When the Actor was created (ISO-8601 string). + /// When the Actor was created (ISO 8601). public string? CreatedAt => GetString("createdAt"); - /// When the Actor was last modified (ISO-8601 string). + /// When the Actor was last modified (ISO 8601). public string? ModifiedAt => GetString("modifiedAt"); - /// - /// The URL of the Actor's server when it is a Server Actor (formerly Actor Standby), otherwise null - /// or absent. - /// + /// The URL of the Actor's server if it is a Server Actor (formerly Actor Standby), otherwise null. public string? StandbyUrl => GetString("standbyUrl"); /// /// The Server Actor settings (actorStandby): isEnabled, build, memoryMbytes, - /// idleTimeoutSecs, tenancy (SINGLE_TENANT or MULTI_TENANT) and others, or - /// null when absent. + /// idleTimeoutSecs, tenancy (SINGLE_TENANT or MULTI_TENANT) and others; null when absent. /// public JsonObject? ActorStandby => Get("actorStandby") as JsonObject; - /// - /// The builds the Actor's tags point to, keyed by tag (e.g. latest); each value holds the - /// buildId and buildNumber, or null when absent. - /// + /// The builds the Actor's tags point to, keyed by tag (latest), each with a buildId and buildNumber; null when absent. public JsonObject? TaggedBuilds => Get("taggedBuilds") as JsonObject; } diff --git a/src/Apify.Client/Models/ActorEnvVar.cs b/src/Apify.Client/Models/ActorEnvVar.cs index ae22924..a74d383 100644 --- a/src/Apify.Client/Models/ActorEnvVar.cs +++ b/src/Apify.Client/Models/ActorEnvVar.cs @@ -58,8 +58,7 @@ public bool? IsSecret set => SetOrRemove("isSecret", value); } - // Honor the documented "null fields are omitted" contract: a null assignment removes the key rather - // than writing a JSON null node. + // A null assignment removes the key instead of writing a JSON null, so null fields stay omitted. private void SetOrRemove(string key, string? value) { if (value is null) diff --git a/src/Apify.Client/Models/ActorRun.cs b/src/Apify.Client/Models/ActorRun.cs index 1487efd..8a8abb6 100644 --- a/src/Apify.Client/Models/ActorRun.cs +++ b/src/Apify.Client/Models/ActorRun.cs @@ -26,18 +26,18 @@ public ActorRun(JsonObject data) public string? UserId => GetString("userId"); /// - /// The current run status. One of the eight ActorJobStatus values: READY, RUNNING, - /// SUCCEEDED, FAILED, TIMING-OUT, TIMED-OUT, ABORTING, ABORTED. + /// The run status: READY, RUNNING, SUCCEEDED, FAILED, TIMING-OUT, TIMED-OUT, + /// ABORTING or ABORTED. /// public string? Status => GetString("status"); /// An optional human-readable status message. public string? StatusMessage => GetString("statusMessage"); - /// When the run started (ISO-8601 string). + /// When the run started (ISO 8601). public string? StartedAt => GetString("startedAt"); - /// When the run finished (absent while still running). + /// When the run finished; absent while it is running. public string? FinishedAt => GetString("finishedAt"); /// The ID of the build used for the run. @@ -52,25 +52,19 @@ public ActorRun(JsonObject data) /// The ID of the run's default request queue. public string? DefaultRequestQueueId => GetString("defaultRequestQueueId"); - /// - /// The URL of the run's container (for live access). Normalized to its RFC 3986 absolute-URI form - /// (see ), matching the reference client. - /// + /// The URL of the run's container, as an RFC 3986 absolute URI (see ). public string? ContainerUrl => GetUrlString("containerUrl"); - /// Whether the run has reached a terminal (finished) status. + /// Whether the run has reached a terminal status. public bool IsTerminal => Statuses.IsTerminal(Status); - /// - /// The URL of the run's page in Apify Console. Normalized to its RFC 3986 absolute-URI form (see - /// ), matching the reference client. - /// + /// The URL of the run in Apify Console, as an RFC 3986 absolute URI (see ). public string? ConsoleUrl => GetUrlString("consoleUrl"); /// - /// The Server Actor deployment of the run: an object whose deployment names the pool serving the run + /// The Server Actor deployment of the run: an object whose deployment names the pool that serves it /// (SINGLE_TENANT, MULTI_TENANT_OWNER_PAID, MULTI_TENANT_FREE_USERS or - /// MULTI_TENANT_PAYING_USERS), or null for a run not started by a Server Actor request. + /// MULTI_TENANT_PAYING_USERS). null for a run not started by a Server Actor request. /// public JsonObject? Standby => Get("standby") as JsonObject; } diff --git a/src/Apify.Client/Models/ActorTask.cs b/src/Apify.Client/Models/ActorTask.cs index 8d4da9b..d4b72cd 100644 --- a/src/Apify.Client/Models/ActorTask.cs +++ b/src/Apify.Client/Models/ActorTask.cs @@ -2,13 +2,8 @@ namespace Apify.Client.Models; -/// -/// A pre-configured Actor run (an Actor task). -/// -/// -/// Named ActorTask rather than Task to avoid colliding with -/// ; it corresponds to the reference client's task resource. -/// +/// An Actor task, a pre-configured Actor run. +/// Named ActorTask to avoid colliding with . public sealed class ActorTask : ApifyResource { /// Wraps a raw task object. @@ -36,16 +31,16 @@ public ActorTask(JsonObject data) /// The human-readable description shown in the UI and on the task's public landing page. public string? Description => GetString("description"); - /// When the task was created (ISO-8601 string). + /// When the task was created (ISO 8601). public string? CreatedAt => GetString("createdAt"); - /// When the task was last modified (ISO-8601 string). + /// When the task was last modified (ISO 8601). public string? ModifiedAt => GetString("modifiedAt"); /// - /// Whether the task is published on its public landing page in Apify Store. Derived from - /// 's publishedAt; set it via - /// or , not by writing this field directly. + /// Whether the task is published on its public landing page in Apify Store, derived from 's + /// publishedAt. Change it with or + /// . /// public bool? IsPublic => GetBool("isPublic"); diff --git a/src/Apify.Client/Models/ActorVersion.cs b/src/Apify.Client/Models/ActorVersion.cs index a268067..630148e 100644 --- a/src/Apify.Client/Models/ActorVersion.cs +++ b/src/Apify.Client/Models/ActorVersion.cs @@ -19,10 +19,9 @@ public ActorVersion(JsonObject data) public string? SourceType => GetString("sourceType"); /// - /// The Actor's directory inside the uploaded source of a SOURCE_FILES or TARBALL version, as a - /// path relative to the upload root with / separators (for example actors/typescript-actor). - /// null when the upload root is the Actor's directory, and absent for a TARBALL version when the - /// caller has no write access to the Actor. + /// The Actor's directory inside the uploaded source of a SOURCE_FILES or TARBALL version, a path relative + /// to the upload root with / separators (for example actors/typescript-actor). null when the upload + /// root is the Actor's directory; absent on a TARBALL version for a caller without write access to the Actor. /// public string? ActorPath => GetString("actorPath"); } diff --git a/src/Apify.Client/Models/ApifyResource.cs b/src/Apify.Client/Models/ApifyResource.cs index ae51f45..0c99715 100644 --- a/src/Apify.Client/Models/ApifyResource.cs +++ b/src/Apify.Client/Models/ApifyResource.cs @@ -6,13 +6,10 @@ namespace Apify.Client.Models; -/// -/// Base class for API resource models. -/// +/// Base class of the API resource models. /// -/// Each model wraps the raw decoded JSON object and exposes commonly-used fields as typed properties. -/// The full payload — including any field the API adds that is not modelled here — is always available -/// via and , so additive API changes never lose data. +/// A model wraps the raw JSON object and exposes common fields as typed properties. Every field, including ones the API +/// adds later, stays available through and . /// public abstract class ApifyResource { @@ -33,6 +30,7 @@ protected ApifyResource(JsonObject data) public JsonNode? Get(string key) => _data.TryGetPropertyValue(key, out var value) ? value : null; /// Reads a string field, coercing numbers to their text form; null if absent or unsupported. + /// The field name. protected string? GetString(string key) { var node = Get(key); @@ -45,11 +43,11 @@ protected ApifyResource(JsonObject data) } /// - /// Reads a field the OpenAPI specification marks format: uri and normalizes it to its RFC 3986 - /// absolute-URI form, matching the reference client's z.url({ normalize: true }): the host is - /// lowercased and punycoded, a default port is dropped, an empty path becomes /, and unsafe - /// characters are percent-encoded. null if the field is absent. + /// Reads a format: uri field and normalizes it to an RFC 3986 absolute URI, as the reference client's + /// z.url({ normalize: true }) does: lowercase punycoded host, no default port, / for an empty path and + /// percent-encoded unsafe characters. null if the field is absent. /// + /// The field name. /// The field is present but not a valid absolute URL. protected string? GetUrlString(string key) { @@ -69,6 +67,7 @@ protected ApifyResource(JsonObject data) } /// Reads an integer field, coercing numeric strings and fractional numbers; null if absent. + /// The field name. protected long? GetInt(string key) { var node = Get(key); @@ -102,6 +101,7 @@ protected ApifyResource(JsonObject data) } /// Reads a boolean field; null if absent or not a JSON boolean. + /// The field name. protected bool? GetBool(string key) { return Get(key)?.GetValueKind() switch @@ -113,6 +113,7 @@ protected ApifyResource(JsonObject data) } /// Reads a string-array field (numbers coerced to text); null if absent or not an array. + /// The field name. protected IReadOnlyList? GetStringList(string key) { if (Get(key) is not JsonArray array) diff --git a/src/Apify.Client/Models/BatchDeleteResult.cs b/src/Apify.Client/Models/BatchDeleteResult.cs index 104774e..39811a2 100644 --- a/src/Apify.Client/Models/BatchDeleteResult.cs +++ b/src/Apify.Client/Models/BatchDeleteResult.cs @@ -4,10 +4,7 @@ namespace Apify.Client.Models; -/// -/// The result of a batch request-delete: the requests that were successfully removed and the ones that -/// could not be (and can be retried). -/// +/// The result of a batch request delete: the requests removed and the ones that failed and can be retried. public sealed class BatchDeleteResult { private BatchDeleteResult(IReadOnlyList processed, IReadOnlyList unprocessed) diff --git a/src/Apify.Client/Models/Build.cs b/src/Apify.Client/Models/Build.cs index 8db96da..fd7a337 100644 --- a/src/Apify.Client/Models/Build.cs +++ b/src/Apify.Client/Models/Build.cs @@ -20,28 +20,27 @@ public Build(JsonObject data) public string? ActId => GetString("actId"); /// - /// The current build status. One of the eight ActorJobStatus values: READY, RUNNING, - /// SUCCEEDED, FAILED, TIMING-OUT, TIMED-OUT, ABORTING, ABORTED. + /// The build status: READY, RUNNING, SUCCEEDED, FAILED, TIMING-OUT, TIMED-OUT, + /// ABORTING or ABORTED. /// public string? Status => GetString("status"); - /// When the build started (ISO-8601 string). + /// When the build started (ISO 8601). public string? StartedAt => GetString("startedAt"); - /// When the build finished (absent while still building). + /// When the build finished; absent while it is running. public string? FinishedAt => GetString("finishedAt"); /// The human-readable build number (e.g. 0.1.2). public string? BuildNumber => GetString("buildNumber"); /// - /// Digest of the built Docker image manifest, without the sha256: prefix. Compare the digests of - /// two builds to find out whether their image contents differ. null if the digest is not - /// available. + /// Digest of the built Docker image manifest, without the sha256: prefix; builds with different digests have + /// different image contents. null if unavailable. /// public string? ImageDigest => GetString("imageDigest"); - /// Whether the build has reached a terminal (finished) status. + /// Whether the build has reached a terminal status. public bool IsTerminal => Statuses.IsTerminal(Status); /// The changelog the Actor's source provided for this build, if any. @@ -56,9 +55,6 @@ public Build(JsonObject data) /// The ID of the Git commit the build's source was taken from, if it came from a Git repository. public string? GitCommitId => GetString("gitCommitId"); - /// - /// The Actor definition the build was made from (the actorDefinition object, including its - /// webServerSchema), or null when absent. - /// + /// The Actor definition of the build (actorDefinition, with its webServerSchema), or null when absent. public JsonObject? ActorDefinition => Get("actorDefinition") as JsonObject; } diff --git a/src/Apify.Client/Models/KeyValueStoreRecord.cs b/src/Apify.Client/Models/KeyValueStoreRecord.cs index 8738880..87ac4c8 100644 --- a/src/Apify.Client/Models/KeyValueStoreRecord.cs +++ b/src/Apify.Client/Models/KeyValueStoreRecord.cs @@ -1,14 +1,10 @@ namespace Apify.Client.Models; -/// -/// A single record retrieved from a key-value store. -/// +/// A record from a key-value store. /// -/// holds the record's raw bytes exactly as stored, so binary records (images, -/// gzip, protobuf, XLSX, …) survive a round-trip intact. Decode it according to : -/// for text use System.Text.Encoding.UTF8.GetString(record.Value), and for JSON -/// (e.g. records written with ) -/// deserialize the bytes with System.Text.Json.JsonSerializer.Deserialize<T>(record.Value). +/// holds the raw bytes exactly as stored, so binary records survive intact. Decode it by +/// : text with System.Text.Encoding.UTF8.GetString(record.Value), JSON with +/// System.Text.Json.JsonSerializer.Deserialize<T>(record.Value). /// public sealed class KeyValueStoreRecord { @@ -26,10 +22,7 @@ public KeyValueStoreRecord(string key, byte[] value, string? contentType) /// The record key. public string Key { get; } - /// - /// The raw record bytes, exactly as stored. Decode according to (see the - /// class remarks for text/JSON decoding). - /// + /// The raw record bytes, exactly as stored. public byte[] Value { get; } /// The record's MIME type, as reported by the API. diff --git a/src/Apify.Client/Models/PaginationList.cs b/src/Apify.Client/Models/PaginationList.cs index 2e31d97..2f470ae 100644 --- a/src/Apify.Client/Models/PaginationList.cs +++ b/src/Apify.Client/Models/PaginationList.cs @@ -6,18 +6,13 @@ namespace Apify.Client.Models; -/// -/// A single page of an offset/limit-paginated list. -/// +/// A page of an offset/limit-paginated list. /// -/// The pagination metadata (, , , -/// , ) accompanies the . is the -/// number of entries the API went through for this page, which can differ from the number of items -/// returned: use Items.Count for the items. The list's own Count (through -/// , so also LINQ's Count()), enumeration and indexer cover the -/// items only. The API's total across all pages is exposed separately as . Note: -/// reflects the API's reported total, which can briefly lag immediately after a write (the -/// count is computed asynchronously) — re-read after a short delay if you need an exact post-write total. +/// The page metadata (, , , , +/// ) accompanies the . is the number of entries the API went +/// through for this page, which can differ from the number of items, so use Items.Count for the items. The +/// list's own Count (, LINQ's Count()), enumeration, and indexer +/// cover the items only. can lag briefly after a write, since the API computes it asynchronously. /// /// The hydrated item type. public sealed class PaginationList : IReadOnlyList @@ -56,10 +51,7 @@ internal static PaginationList FromData(JsonNode? data, Func h JsonValues.BoolOr(obj, "desc", false)); } - /// - /// Builds a page directly from items and metadata (used by the dataset-items endpoint, which returns - /// a bare array with pagination in response headers). - /// + /// Builds a page from items and metadata, for the dataset-items endpoint, which returns a bare array. /// The page items. /// The rows the API scanned for the page, or the item count when that is larger. /// Total number of items available across all pages. @@ -69,7 +61,7 @@ internal static PaginationList FromData(JsonNode? data, Func h internal static PaginationList FromItems(IReadOnlyList items, long count, long total, long offset, long limit, bool desc) => new(items, count, total, offset, limit, desc); - /// The items of this page (never null). + /// The items of this page. public IReadOnlyList Items => _items; /// Total number of items available across all pages. @@ -82,15 +74,13 @@ internal static PaginationList FromItems(IReadOnlyList items, long count, public long Limit { get; } /// - /// Number of entries on this page. On a page from , the - /// number of dataset rows the API scanned for it, or the number of items returned when that is larger, so - /// filters such as or - /// can leave it above Items.Count. The next page - /// starts at Offset + Math.Min(Count, Limit); the cap matters only with - /// , which can return more items than the rows scanned. - /// On any other page it is the count the API reports, which is the number of items returned except - /// where the API drops entries after counting them, as the Store listing does. Use - /// for the count across all pages. + /// The number of entries on this page. For it is the number of + /// dataset rows the API scanned, or the number of items if that is larger, so filters such as + /// or can + /// leave it above Items.Count. The next page starts at Offset + Math.Min(Count, Limit); the cap matters + /// with , which can return more items than rows. For other pages it + /// is the count the API reports, which equals the items returned unless the API drops entries after counting + /// them, as the Store listing does. is the count across all pages. /// public long Count { get; } diff --git a/src/Apify.Client/Models/RequestQueueOperationInfo.cs b/src/Apify.Client/Models/RequestQueueOperationInfo.cs index 9f2270a..8f5df8c 100644 --- a/src/Apify.Client/Models/RequestQueueOperationInfo.cs +++ b/src/Apify.Client/Models/RequestQueueOperationInfo.cs @@ -15,10 +15,7 @@ public RequestQueueOperationInfo(JsonObject data) /// The ID of the affected request. public string? RequestId => GetString("requestId"); - /// - /// The unique key of the affected request. Populated for batch-add results; may be null for - /// single add/update operations. - /// + /// The unique key of the affected request. Set for batch-add results, may be null otherwise. public string? UniqueKey => GetString("uniqueKey"); /// Whether the request was already in the queue. diff --git a/src/Apify.Client/Models/RequestQueueRequest.cs b/src/Apify.Client/Models/RequestQueueRequest.cs index 9f4628f..016d932 100644 --- a/src/Apify.Client/Models/RequestQueueRequest.cs +++ b/src/Apify.Client/Models/RequestQueueRequest.cs @@ -2,10 +2,7 @@ namespace Apify.Client.Models; -/// -/// A single request stored in a request queue. Fields left null are omitted when the request is -/// sent to the API. Construct one for adding to a queue, or receive one when reading a queue. -/// +/// A request in a request queue. null fields are omitted when the request is sent to the API. public sealed class RequestQueueRequest : ApifyResource { private RequestQueueRequest(JsonObject data) @@ -62,10 +59,7 @@ public string? Method set => SetString("method", value); } - /// - /// The number of times this request has been retried after a failed processing attempt (assigned by - /// the API; absent on create). - /// + /// How many times the request was retried after a failed processing attempt (assigned by the API). public long? RetryCount { get => GetInt("retryCount"); @@ -83,8 +77,8 @@ public long? RetryCount } /// - /// When this request's processing lock expires (ISO-8601 string). Only present on requests returned - /// by . + /// When the request's lock expires (ISO 8601). Only on requests from + /// . /// public string? LockExpiresAt { @@ -93,8 +87,8 @@ public string? LockExpiresAt } /// - /// The client key of the client that currently holds this request's processing lock. Only present on - /// locked requests returned by . + /// The client key of the client that holds the request's lock. Only on locked requests from + /// . /// public string? LockByClient { @@ -102,9 +96,7 @@ public string? LockByClient set => SetString("lockByClient", value); } - /// - /// When this request was handled (ISO-8601 string); null while the request is still pending. - /// + /// When the request was handled (ISO 8601); null while it is pending. public string? HandledAt { get => GetString("handledAt"); diff --git a/src/Apify.Client/Models/RequestQueueRequestsPage.cs b/src/Apify.Client/Models/RequestQueueRequestsPage.cs index b2f46f0..8aaabbb 100644 --- a/src/Apify.Client/Models/RequestQueueRequestsPage.cs +++ b/src/Apify.Client/Models/RequestQueueRequestsPage.cs @@ -4,10 +4,7 @@ namespace Apify.Client.Models; -/// -/// A single, cursor-paginated page of a request queue's requests (as returned by -/// ). -/// +/// A cursor-paginated page of requests, from . public sealed class RequestQueueRequestsPage { private RequestQueueRequestsPage( diff --git a/src/Apify.Client/Models/TaskPublicConfig.cs b/src/Apify.Client/Models/TaskPublicConfig.cs index 4776e7e..02ebc16 100644 --- a/src/Apify.Client/Models/TaskPublicConfig.cs +++ b/src/Apify.Client/Models/TaskPublicConfig.cs @@ -3,13 +3,10 @@ namespace Apify.Client.Models; -/// -/// Public-facing display configuration for a task's public landing page in Apify Store. -/// +/// Display configuration of a task's public landing page in Apify Store. /// -/// The task is published when is set and unpublished when it is -/// null. is read-only; use -/// and to change the publication state. +/// The task is published when is set. It is read-only; use +/// and . /// public sealed class TaskPublicConfig : ApifyResource { @@ -20,7 +17,7 @@ public TaskPublicConfig(JsonObject data) { } - /// When the task was published (ISO-8601 string), or null if it is not published. + /// When the task was published (ISO 8601), or null if it is not. public string? PublishedAt => GetString("publishedAt"); /// Name to display for search engines such as Google. @@ -29,11 +26,7 @@ public TaskPublicConfig(JsonObject data) /// Description to display for search engines such as Google. public string? SeoDescription => GetString("seoDescription"); - /// - /// The task's category on its public landing page. Not part of the documented OpenAPI schema for - /// this resource; kept for parity with the reference JS client, which exposes the same - /// undocumented field. - /// + /// The category of the task on its public landing page. Not in the OpenAPI schema; kept because the reference client exposes it. public string? Categorization => GetString("categorization"); /// The input schema fields shown on the public landing page. diff --git a/src/Apify.Client/Models/Webhook.cs b/src/Apify.Client/Models/Webhook.cs index 0627c65..aab04fd 100644 --- a/src/Apify.Client/Models/Webhook.cs +++ b/src/Apify.Client/Models/Webhook.cs @@ -19,10 +19,7 @@ public Webhook(JsonObject data) /// The ID of the user who owns the webhook. public string? UserId => GetString("userId"); - /// - /// The URL the webhook posts to. Normalized to its RFC 3986 absolute-URI form (see - /// ), matching the reference client. - /// + /// The URL the webhook posts to, as an RFC 3986 absolute URI (see ). public string? RequestUrl => GetUrlString("requestUrl"); /// The events that trigger the webhook. diff --git a/src/Apify.Client/Options/ActorListOptions.cs b/src/Apify.Client/Options/ActorListOptions.cs index 442b30c..f0702a4 100644 --- a/src/Apify.Client/Options/ActorListOptions.cs +++ b/src/Apify.Client/Options/ActorListOptions.cs @@ -12,9 +12,8 @@ public sealed class ActorListOptions public int? Limit { get; init; } /// - /// Maximum number of items requested per API call when iterating across pages (must be positive). Only - /// iteration uses it, as the page size; it is never sent to the API. The effective page size is the - /// smaller of this and the remaining Limit. Defaults to the API's own page size. + /// Maximum items requested per API call when iterating, which must be positive. It sizes the pages and is never sent to + /// the API; the page size is the smaller of this and the remaining Limit. Defaults to the API's page size. /// public int? ChunkSize { get; init; } diff --git a/src/Apify.Client/Options/ActorServerRequestOptions.cs b/src/Apify.Client/Options/ActorServerRequestOptions.cs index e7b4302..860ec1b 100644 --- a/src/Apify.Client/Options/ActorServerRequestOptions.cs +++ b/src/Apify.Client/Options/ActorServerRequestOptions.cs @@ -12,26 +12,24 @@ public sealed class ActorServerRequestOptions public HttpMethod? Method { get; init; } /// - /// The query parameters of the request, converted as for API calls: null values are left out, booleans - /// become 1/0, dates become ISO 8601 in UTC (a of unspecified kind is - /// taken as UTC), lists become their comma-joined values and other values use their invariant text form. A - /// query in the path is kept and these are appended to it. + /// The query parameters, converted as for API calls: null values are left out, booleans become 1/0, + /// dates ISO 8601 in UTC (a of unspecified kind is taken as UTC), lists their comma-joined values + /// and other values their invariant text. A query in the path is kept and these are appended. /// public IReadOnlyDictionary? Query { get; init; } /// /// Additional request headers. They reach the Actor's server as they are, Authorization included, and - /// a header of the token's name (X-Apify-Authorization) replaces the token. A Content-Type header - /// is the same as . A Content-Encoding header marks a body that is already - /// encoded, which is then sent without compressing it (identity opts out of compression). Other - /// Content-* headers, such as Content-Disposition, describe the body and are sent only with a - /// ; a Content-Length header is ignored, since the client sets the length itself. + /// X-Apify-Authorization replaces the token. Content-Type equals . A + /// Content-Encoding marks a body that is already encoded and is sent without compressing it (identity opts + /// out of compression). Other Content-* headers, such as Content-Disposition, are sent only with a + /// . Content-Length is ignored, since the client sets it. /// public IReadOnlyDictionary? Headers { get; init; } /// - /// The request body, serialized as for API calls: raw bytes (byte[]) and a string are sent as - /// they are, and any other value is serialized to JSON. null sends no body. + /// The request body: byte[] and string are sent as they are, any other value is serialized to JSON, and + /// null sends no body. /// public object? Body { get; init; } @@ -42,24 +40,29 @@ public sealed class ActorServerRequestOptions public string? ContentType { get; init; } /// - /// The compressor for the request body, for example . The body is sent - /// uncompressed when it is not set, since the Actor's server may not decode a compressed body. The body is - /// compressed under the same conditions as for API calls. Set it only if the Actor's server decodes the + /// The X-Actor-Request-ID header of the request a multi-tenant Server Actor is handling, sent on as the + /// same header so the called Actor charges at that request's pricing tier. It replaces an + /// X-Actor-Request-ID in . Use a client created with the run's APIFY_TOKEN. + /// + public string? ParentRequestId { get; init; } + + /// + /// The compressor of the request body, for example . Without it the body is sent + /// uncompressed, since the Actor's server may not decode a compressed body. Set it only if the server decodes the /// resulting Content-Encoding. Matches the reference client's compression option. /// public HttpCompressor? Compressor { get; init; } /// - /// Whether to return the response body unread, to read it as a stream with - /// . The response then holds its connection open - /// until it is disposed. Defaults to false, which reads the whole body before returning. + /// Whether to return the response body unread, to read it with . + /// The response holds its connection until disposed. Defaults to false, which reads the whole body first. /// public bool StreamResponse { get; init; } /// - /// The timeout of the HTTP request. Defaults to the client's Long timeout tier, which covers the cold - /// start of an idle Actor. With , it bounds the wait for the server to start - /// responding. A value above is capped at it. + /// The timeout of the HTTP request. Defaults to the Long tier, which covers the cold start of an idle Actor. With + /// it bounds the wait for the server to start responding. A value above + /// is capped at it. /// public TimeSpan? Timeout { get; init; } } diff --git a/src/Apify.Client/Options/ActorStartOptions.cs b/src/Apify.Client/Options/ActorStartOptions.cs index badc4d7..e2effef 100644 --- a/src/Apify.Client/Options/ActorStartOptions.cs +++ b/src/Apify.Client/Options/ActorStartOptions.cs @@ -32,21 +32,16 @@ public sealed class ActorStartOptions public bool? RestartOnError { get; init; } /// - /// Retries the start while the account lacks the memory or a concurrent-run slot for the run (an - /// actor-memory-limit-exceeded/concurrent-runs-limit-exceeded error), instead of throwing - /// immediately. null (the default) disables retrying. + /// Retries the start while the account lacks the memory or a concurrent-run slot + /// (actor-memory-limit-exceeded, concurrent-runs-limit-exceeded) instead of throwing. null (the + /// default) does not retry. /// public WaitForResources? WaitForResources { get; init; } - /// - /// Override the Actor's permission level for this run (LIMITED_PERMISSIONS/FULL_PERMISSIONS). - /// + /// Overrides the Actor's permission level for this run (LIMITED_PERMISSIONS or FULL_PERMISSIONS). public string? ForcePermissionLevel { get; init; } - /// - /// Ad-hoc webhooks to attach to this run; a JSON-serializable list serialized to base64-encoded JSON - /// as the webhooks query parameter. - /// + /// Ad-hoc webhooks for this run: a JSON-serializable list, sent as base64-encoded JSON in the webhooks query parameter. public object? Webhooks { get; init; } /// The configured content type, or the JSON default when unset. @@ -67,9 +62,8 @@ internal void AppendTo(QueryParams q) } /// - /// Encodes an ad-hoc webhooks list as base64-encoded JSON, as the API's webhooks query - /// parameter requires. Returns null for a null list. Shared by Actor and task start - /// options. + /// Encodes ad-hoc webhooks as base64-encoded JSON for the webhooks query parameter; null for null. + /// Shared by the Actor and task start options. /// internal static string? EncodeWebhooks(object? webhooks) { diff --git a/src/Apify.Client/Options/BatchAddRequestsOptions.cs b/src/Apify.Client/Options/BatchAddRequestsOptions.cs index 3c67ec0..cb59b95 100644 --- a/src/Apify.Client/Options/BatchAddRequestsOptions.cs +++ b/src/Apify.Client/Options/BatchAddRequestsOptions.cs @@ -2,19 +2,16 @@ namespace Apify.Client.Options; -/// -/// Tuning options for batch request adding, mirroring the reference client. Requests the API reports as -/// unprocessed (typically due to rate limiting) are automatically retried with exponential backoff. -/// +/// Tuning options of batch request adding. Requests the API reports as unprocessed are retried with exponential backoff. public sealed class BatchAddRequestsOptions { - /// Default number of retry rounds for unprocessed requests (matches the reference client). + /// Default number of retry rounds for unprocessed requests, as in the reference client. public const int DefaultMaxUnprocessedRetries = 3; - /// Default maximum number of batch API calls made in parallel (matches the reference client). + /// Default maximum number of parallel batch API calls, as in the reference client. public const int DefaultMaxParallel = 5; - /// Default minimum delay before retrying unprocessed requests (matches the reference client). + /// Default minimum delay before retrying unprocessed requests, as in the reference client. public const int DefaultMinDelayMillis = 500; /// Creates batch-add options, clamping values to their valid ranges. diff --git a/src/Apify.Client/Options/DatasetDownloadOptions.cs b/src/Apify.Client/Options/DatasetDownloadOptions.cs index eab665f..0349404 100644 --- a/src/Apify.Client/Options/DatasetDownloadOptions.cs +++ b/src/Apify.Client/Options/DatasetDownloadOptions.cs @@ -2,10 +2,7 @@ namespace Apify.Client.Options; -/// -/// Adds format-specific options for downloading dataset items on top of the shared item -/// filtering/projection options (). -/// +/// Format-specific options for downloading dataset items, on top of the filtering and projection options of . public sealed class DatasetDownloadOptions { /// The shared filtering/projection options. diff --git a/src/Apify.Client/Options/DatasetListItemsOptions.cs b/src/Apify.Client/Options/DatasetListItemsOptions.cs index 77903eb..826c88a 100644 --- a/src/Apify.Client/Options/DatasetListItemsOptions.cs +++ b/src/Apify.Client/Options/DatasetListItemsOptions.cs @@ -3,28 +3,22 @@ namespace Apify.Client.Options; -/// -/// Configures listing or downloading dataset items (GET /v2/datasets/{datasetId}/items). All -/// fields are optional. -/// +/// Configures listing or downloading dataset items. All fields are optional. public sealed class DatasetListItemsOptions { /// Number of items to skip. public int? Offset { get; init; } /// - /// Maximum number of dataset rows to read. Filters can return fewer items than rows read and - /// more. When iterating, it caps the rows read in total across all pages; 0 - /// or unset means no cap. + /// Maximum number of dataset rows to read. Filters can return fewer items than rows and more. When + /// iterating, it caps the rows read across all pages; 0 or unset means no cap. /// public int? Limit { get; init; } /// - /// Maximum number of items requested per API call when iterating across pages (must be positive). Only - /// iteration uses it, as the page size; it is never sent to the API. The effective page size is the - /// smaller of this and the remaining Limit. Dataset items have no default limit, so without - /// ChunkSize and Limit iteration loads the whole dataset in one response; set it to iterate a - /// large dataset. + /// Maximum rows requested per API call when iterating (must be positive); never sent to the API. The page size is the + /// smaller of this and the remaining Limit. Without ChunkSize and Limit, iteration loads the whole + /// dataset in one response, so set it for a large dataset. /// public int? ChunkSize { get; init; } diff --git a/src/Apify.Client/Options/IterateKeysOptions.cs b/src/Apify.Client/Options/IterateKeysOptions.cs index 8385dc6..6899126 100644 --- a/src/Apify.Client/Options/IterateKeysOptions.cs +++ b/src/Apify.Client/Options/IterateKeysOptions.cs @@ -1,10 +1,6 @@ namespace Apify.Client.Options; -/// -/// Configures lazy iteration over a key-value store's keys -/// (), mirroring the reference -/// client's chunked listKeys() iteration (limit, chunkSize, ...). -/// +/// Configures , as the reference client's chunked listKeys(). public sealed class IterateKeysOptions { /// Default maximum number of keys fetched per page (matches the API's own default/maximum). @@ -14,16 +10,12 @@ public sealed class IterateKeysOptions public int? Limit { get; init; } /// - /// Maximum number of keys requested per API call (must be positive; defaults to - /// ). Never sent to the API as-is; it only sizes each page request. - /// Matches the reference client's chunkSize. + /// Maximum keys requested per API call, which must be positive (default ). It only + /// sizes the pages and is never sent to the API. As the reference client's chunkSize. /// public int? ChunkSize { get; init; } - /// - /// The earlier name of , kept so existing callers keep working. - /// takes precedence when both are set. - /// + /// The earlier name of ; wins when both are set. public int? MaxPageLimit { get; init; } /// Start iterating after this key (first page only). diff --git a/src/Apify.Client/Options/LastRunOptions.cs b/src/Apify.Client/Options/LastRunOptions.cs index fd32f95..c5842c2 100644 --- a/src/Apify.Client/Options/LastRunOptions.cs +++ b/src/Apify.Client/Options/LastRunOptions.cs @@ -1,15 +1,10 @@ namespace Apify.Client.Options; -/// -/// Filters which "last" run the last-run accessors resolve to. Leave a field null to leave that -/// filter unset. -/// +/// Filters which last run the last-run accessors resolve to. A null field leaves that filter unset. /// -/// Status and Origin are both spec-declared query parameters on the last-run endpoints -/// (GET /v2/actors/{actorId}/runs/last and GET /v2/actor-tasks/{actorTaskId}/runs/last), -/// matching the reference client's lastRun({ status, origin }). The spec also declares -/// waitForFinish on those endpoints, but the reference client does not expose it on lastRun, -/// so it is intentionally omitted here for parity. +/// Status and Origin are the query parameters of the last-run endpoints, as in the reference client's +/// lastRun({ status, origin }). waitForFinish is left out because the reference client does not expose it +/// there. /// public sealed class LastRunOptions { diff --git a/src/Apify.Client/Options/ListOptions.cs b/src/Apify.Client/Options/ListOptions.cs index 521899c..0eb57cd 100644 --- a/src/Apify.Client/Options/ListOptions.cs +++ b/src/Apify.Client/Options/ListOptions.cs @@ -3,9 +3,8 @@ namespace Apify.Client.Options; /// -/// The standard offset/limit pagination shared by most list endpoints (builds, runs, tasks, -/// schedules, webhooks, Actor versions). All fields are optional; leave one null to use the API -/// default. +/// The offset/limit pagination shared by the list endpoints (builds, runs, tasks, schedules, webhooks, Actor versions). +/// All fields are optional; null uses the API default. /// public sealed class ListOptions { @@ -16,9 +15,8 @@ public sealed class ListOptions public int? Limit { get; init; } /// - /// Maximum number of items requested per API call when iterating across pages (must be positive). Only - /// iteration uses it, as the page size; it is never sent to the API. The effective page size is the - /// smaller of this and the remaining Limit. Defaults to the API's own page size. + /// Maximum items requested per API call when iterating, which must be positive. It sizes the pages and is never sent to + /// the API; the page size is the smaller of this and the remaining Limit. Defaults to the API's page size. /// public int? ChunkSize { get; init; } diff --git a/src/Apify.Client/Options/PaginateRequestsOptions.cs b/src/Apify.Client/Options/PaginateRequestsOptions.cs index 3a3f008..39c993b 100644 --- a/src/Apify.Client/Options/PaginateRequestsOptions.cs +++ b/src/Apify.Client/Options/PaginateRequestsOptions.cs @@ -6,9 +6,8 @@ namespace Apify.Client.Options; /// -/// Configures lazy iteration over a request queue's requests -/// (), mirroring the -/// reference client's paginateRequests({ limit, maxPageLimit, exclusiveStartId, cursor, filter }). +/// Configures , as the reference client's +/// paginateRequests. /// public sealed class PaginateRequestsOptions { @@ -25,15 +24,12 @@ public sealed class PaginateRequestsOptions public int? Limit { get; init; } /// - /// Maximum number of requests fetched per API call (must be positive; defaults to - /// ). Matches the reference client's chunkSize. + /// Maximum requests fetched per API call, which must be positive (default ). As the + /// reference client's chunkSize. /// public int? ChunkSize { get; init; } - /// - /// The earlier name of , kept so existing callers keep working. - /// takes precedence when both are set. - /// + /// The earlier name of ; wins when both are set. public int? MaxPageLimit { get; init; } /// Start iterating after this request ID (first page only; mutually exclusive with cursor). @@ -42,10 +38,7 @@ public sealed class PaginateRequestsOptions /// An opaque pagination cursor to start from (mutually exclusive with ). public string? Cursor { get; init; } - /// - /// Restrict the iteration to requests in the given states; each value must be - /// or . - /// + /// Restricts the iteration to requests in the given states, each or . public IReadOnlyList? Filter { get; init; } /// Validates the options for API-level constraints. diff --git a/src/Apify.Client/Options/RequestQueueClientOptions.cs b/src/Apify.Client/Options/RequestQueueClientOptions.cs index 16f1a51..1bb1cef 100644 --- a/src/Apify.Client/Options/RequestQueueClientOptions.cs +++ b/src/Apify.Client/Options/RequestQueueClientOptions.cs @@ -1,21 +1,17 @@ namespace Apify.Client.Options; -/// -/// Per-client options for a , mirroring the -/// reference client's requestQueue(id, { clientKey, timeoutSecs }). -/// +/// Options of a , as the reference client's requestQueue(id, { clientKey, timeoutSecs }). public sealed class RequestQueueClientOptions { /// - /// A stable client key identifying this client to the queue. Required to operate on locks the client - /// itself created, and lets the API detect whether multiple clients access the queue. + /// A stable key that identifies this client to the queue. Needed to operate on locks the client created, and lets the API + /// detect several clients on one queue. /// public string? ClientKey { get; init; } /// - /// Per-request timeout (seconds) for this queue client's calls. It shortens the wait for each call and - /// is capped at the client-wide overall timeout, so a value larger than that timeout has no effect. When - /// null the shared client-wide timeout is used. + /// Per-request timeout in seconds for this queue client's calls, capped at the client-wide overall timeout. null + /// uses the client-wide timeout. /// public double? TimeoutSecs { get; init; } } diff --git a/src/Apify.Client/Options/RunChargeOptions.cs b/src/Apify.Client/Options/RunChargeOptions.cs index 3e82fc0..a224226 100644 --- a/src/Apify.Client/Options/RunChargeOptions.cs +++ b/src/Apify.Client/Options/RunChargeOptions.cs @@ -7,8 +7,8 @@ public sealed class RunChargeOptions /// The name of the event to charge for. Required. /// The number of times to charge the event (defaults to 1). /// - /// A key that deduplicates the charge across retries. If unset, one is auto-generated as - /// "{runId}-{eventName}-{timestampMillis}-{random}", matching the reference client. + /// A key that deduplicates the charge across retries. If unset, {runId}-{eventName}-{timestampMillis}-{random} is + /// generated, as in the reference client. /// public RunChargeOptions(string eventName, int? count = null, string? idempotencyKey = null) { @@ -27,9 +27,8 @@ public RunChargeOptions(string eventName, int? count = null, string? idempotency public string? IdempotencyKey { get; } /// - /// The ID of the Server Actor request being charged, taken from the 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. + /// The ID of the Server Actor request being charged, from the X-Actor-Request-ID header of the incoming request. + /// Required for multi-tenant Server Actor runs. Runs of single-tenant Server Actors ignore it. /// public string? RequestId { get; init; } diff --git a/src/Apify.Client/Options/RunIterateDatasetItemsOptions.cs b/src/Apify.Client/Options/RunIterateDatasetItemsOptions.cs index abd5bd7..fc4e6a1 100644 --- a/src/Apify.Client/Options/RunIterateDatasetItemsOptions.cs +++ b/src/Apify.Client/Options/RunIterateDatasetItemsOptions.cs @@ -3,9 +3,9 @@ namespace Apify.Client.Options; /// -/// Configures , which iterates over -/// the items of a run's default dataset while the run is still producing them. Mirrors the reference -/// client's RunIterateDatasetItemsOptions. All fields are optional. +/// Configures , which iterates the items of a +/// run's default dataset while the run is still producing them. As the reference client's +/// RunIterateDatasetItemsOptions. All fields are optional. /// public sealed class RunIterateDatasetItemsOptions { @@ -19,8 +19,8 @@ public sealed class RunIterateDatasetItemsOptions public int? Offset { get; init; } /// - /// Maximum number of dataset rows to scan (null or 0 for no limit). Fewer items are - /// yielded when filters drop some, more when splits a row into several. + /// Maximum dataset rows to scan (null or 0 for no limit). Filters can drop items and + /// can split a row into several. /// public int? Limit { get; init; } diff --git a/src/Apify.Client/Options/RunListOptions.cs b/src/Apify.Client/Options/RunListOptions.cs index 2243e20..748a06c 100644 --- a/src/Apify.Client/Options/RunListOptions.cs +++ b/src/Apify.Client/Options/RunListOptions.cs @@ -4,23 +4,19 @@ namespace Apify.Client.Options; /// -/// Run-specific filters for listing runs. The StartedAfter/StartedBefore filters are only -/// honoured by the account-wide and Actor-scoped run collections, not by task runs. +/// Run-specific filters for listing runs. StartedAfter and StartedBefore apply only to the account-wide and +/// Actor-scoped run collections, not to task runs. /// public sealed class RunListOptions { - /// - /// Filter by one or more run statuses (e.g. SUCCEEDED, RUNNING); sent as a - /// comma-separated list. - /// + /// Filters by one or more run statuses (SUCCEEDED, RUNNING), sent as a comma-separated list. public IReadOnlyList? Status { get; init; } /// - /// Filter Actor Standby runs by the pool that serves them: one or more of SINGLE_TENANT, - /// MULTI_TENANT_FREE_USERS, MULTI_TENANT_PAYING_USERS or MULTI_TENANT_OWNER_PAID; sent - /// as a comma-separated list. Requires with only non-terminal statuses (READY, - /// RUNNING, TIMING-OUT or ABORTING). Only the account-wide and Actor-scoped run - /// collections honour it. + /// Filters Server Actor runs by the pool that serves them: SINGLE_TENANT, MULTI_TENANT_FREE_USERS, + /// MULTI_TENANT_PAYING_USERS or MULTI_TENANT_OWNER_PAID, sent as a comma-separated list. Needs + /// with only non-terminal statuses (READY, RUNNING, TIMING-OUT or + /// ABORTING). Only the account-wide and Actor-scoped run collections honor it. /// public IReadOnlyList? Deployment { get; init; } diff --git a/src/Apify.Client/Options/SetRecordOptions.cs b/src/Apify.Client/Options/SetRecordOptions.cs index 41acf18..dc780ca 100644 --- a/src/Apify.Client/Options/SetRecordOptions.cs +++ b/src/Apify.Client/Options/SetRecordOptions.cs @@ -1,26 +1,18 @@ namespace Apify.Client.Options; -/// -/// Write options for storing a key-value-store record, mirroring the reference client's -/// timeoutSecs/doNotRetryTimeouts. -/// +/// Write options of a key-value store record, as the reference client's timeoutSecs and doNotRetryTimeouts. public sealed class SetRecordOptions { - /// - /// Per-request timeout for the upload, in seconds. Use it to shorten the wait for this upload; defaults - /// to (and is capped at) the client's configured overall request timeout, so a value larger than that - /// timeout has no effect. - /// + /// Per-request timeout of the upload in seconds, capped at the client's overall request timeout. public int? TimeoutSecs { get; init; } /// If true, do not retry the upload when it fails with a request timeout. public bool DoNotRetryTimeouts { get; init; } /// - /// Encoding already applied to the record value, sent verbatim as the Content-Encoding header. - /// Set it to upload a pre-compressed value (gzip, br, deflate, or identity): - /// the client then sends the bytes as they are and skips its own compression, so they are never - /// compressed twice. Must not be empty or whitespace-only. + /// The encoding already applied to the value, sent as the Content-Encoding header, to upload a pre-compressed value + /// (gzip, br, zstd, deflate, or identity). The client sends the bytes as they are and + /// skips its own compression. Must not be empty or whitespace-only. /// public string? ContentEncoding { get; init; } } diff --git a/src/Apify.Client/Options/StorageListOptions.cs b/src/Apify.Client/Options/StorageListOptions.cs index 259162e..df2880a 100644 --- a/src/Apify.Client/Options/StorageListOptions.cs +++ b/src/Apify.Client/Options/StorageListOptions.cs @@ -2,11 +2,7 @@ namespace Apify.Client.Options; -/// -/// Options for the storage collection list endpoints (GET /v2/datasets, -/// /v2/key-value-stores, /v2/request-queues), which add unnamed and ownership -/// filters on top of the standard pagination. -/// +/// Options for listing datasets, key-value stores, and request queues: pagination plus unnamed and ownership filters. public sealed class StorageListOptions { /// Number of items to skip from the beginning of the list. @@ -16,9 +12,8 @@ public sealed class StorageListOptions public int? Limit { get; init; } /// - /// Maximum number of items requested per API call when iterating across pages (must be positive). Only - /// iteration uses it, as the page size; it is never sent to the API. The effective page size is the - /// smaller of this and the remaining Limit. Defaults to the API's own page size. + /// Maximum items requested per API call when iterating, which must be positive. It sizes the pages and is never sent to + /// the API; the page size is the smaller of this and the remaining Limit. Defaults to the API's page size. /// public int? ChunkSize { get; init; } diff --git a/src/Apify.Client/Options/StoreListOptions.cs b/src/Apify.Client/Options/StoreListOptions.cs index f957b1b..1dca04f 100644 --- a/src/Apify.Client/Options/StoreListOptions.cs +++ b/src/Apify.Client/Options/StoreListOptions.cs @@ -13,9 +13,8 @@ public sealed class StoreListOptions public int? Limit { get; init; } /// - /// Maximum number of items requested per API call when iterating across pages (must be positive). Only - /// iteration uses it, as the page size; it is never sent to the API. The effective page size is the - /// smaller of this and the remaining Limit. Defaults to the API's own page size. + /// Maximum items requested per API call when iterating, which must be positive. It sizes the pages and is never sent to + /// the API; the page size is the smaller of this and the remaining Limit. Defaults to the API's page size. /// public int? ChunkSize { get; init; } @@ -44,9 +43,9 @@ public sealed class StoreListOptions public bool? AllowsAgenticUsers { get; init; } /// - /// Include each Actor's inputSchema: a compact version of the input schema of its default build - /// (property types and required properties only). With it, the API's limit defaults to and cannot - /// exceed , so iteration requests pages of at most that size. + /// Include each Actor's inputSchema, a compact form of the input schema of its default build (property types and + /// required properties). The API then caps limit at , and iteration + /// requests pages of at most that size. /// public bool? IncludeInputSchema { get; init; } diff --git a/src/Apify.Client/Options/TaskStartOptions.cs b/src/Apify.Client/Options/TaskStartOptions.cs index f11ee5b..a78426c 100644 --- a/src/Apify.Client/Options/TaskStartOptions.cs +++ b/src/Apify.Client/Options/TaskStartOptions.cs @@ -2,12 +2,10 @@ namespace Apify.Client.Options; -/// -/// Configures starting a task run. -/// +/// Configures starting a task run. /// -/// It mirrors but omits the fields the task run endpoint does not accept -/// (the Actor-only ContentType and ForcePermissionLevel), matching the reference client. +/// Like without the fields the task run endpoint does not accept (ContentType and +/// ForcePermissionLevel), as in the reference client. /// public sealed class TaskStartOptions { @@ -33,9 +31,9 @@ public sealed class TaskStartOptions public bool? RestartOnError { get; init; } /// - /// Retries the start while the account lacks the memory or a concurrent-run slot for the run (an - /// actor-memory-limit-exceeded/concurrent-runs-limit-exceeded error), instead of throwing - /// immediately. null (the default) disables retrying. + /// Retries the start while the account lacks the memory or a concurrent-run slot + /// (actor-memory-limit-exceeded, concurrent-runs-limit-exceeded) instead of throwing. null (the + /// default) does not retry. /// public WaitForResources? WaitForResources { get; init; } diff --git a/src/Apify.Client/Options/WaitForResources.cs b/src/Apify.Client/Options/WaitForResources.cs index be3b33a..175a9b4 100644 --- a/src/Apify.Client/Options/WaitForResources.cs +++ b/src/Apify.Client/Options/WaitForResources.cs @@ -3,12 +3,11 @@ namespace Apify.Client.Options; /// -/// Configures retrying a run start while the account has no free memory or concurrent-run slot for it (the -/// API's actor-memory-limit-exceeded/concurrent-runs-limit-exceeded errors, which clear as -/// other runs or builds finish). Passed to and -/// ; matches the reference client's waitForResources -/// start option (there a boolean | number union, modelled here as this small value type so the -/// "retry forever" and "retry for N seconds" cases stay distinct and self-documenting). +/// Configures retrying a run start while the account has no free memory or concurrent-run slot +/// (actor-memory-limit-exceeded, concurrent-runs-limit-exceeded), which clears as other runs finish. Passed +/// to and . The reference +/// client's waitForResources is a boolean | number; this value type keeps "retry forever" and "retry for N +/// seconds" distinct. /// public readonly struct WaitForResources : IEquatable { @@ -17,22 +16,16 @@ private WaitForResources(double? maxWaitSecs) MaxWaitSecs = maxWaitSecs; } - /// - /// The maximum time to keep retrying, in seconds, or null when retrying until the run starts - /// (see ). - /// + /// The longest time to keep retrying in seconds, or null to retry until the run starts (see ). public double? MaxWaitSecs { get; } /// Retries indefinitely until the run starts. public static WaitForResources UntilAvailable { get; } = new(null); - /// - /// Retries for up to seconds, after which the last - /// actor-memory-limit-exceeded/concurrent-runs-limit-exceeded error is thrown. - /// + /// Retries for up to seconds, then throws the last resource-limit error. /// - /// The retry budget in seconds; must be finite and not negative (use , - /// not , to retry forever). + /// The retry budget in seconds; finite and not negative. Use , not + /// , to retry forever. /// public static WaitForResources For(double maxWaitSecs) { diff --git a/src/Apify.Client/RequestCompression.cs b/src/Apify.Client/RequestCompression.cs index 880626c..90ec6f8 100644 --- a/src/Apify.Client/RequestCompression.cs +++ b/src/Apify.Client/RequestCompression.cs @@ -1,18 +1,18 @@ namespace Apify.Client; -/// -/// Algorithm used to compress large request bodies before sending them. -/// +/// Algorithm used to compress large request bodies. /// -/// Mirrors the reference client's explicit compression: 'brotli' | 'gzip' option, which defaults to -/// brotli. Regardless of the choice, only string/byte payloads at or above the compression threshold are -/// compressed. +/// Mirrors the reference client's compression option, which defaults to brotli. Only string or byte payloads of +/// at least 1024 bytes are compressed. /// public enum RequestCompression { - /// Compress with brotli (Content-Encoding: br). This is the default and matches the reference client's preference. + /// Compress with brotli (Content-Encoding: br). This is the default. Brotli, /// Compress with gzip (Content-Encoding: gzip). Gzip, + + /// Compress with zstd (Content-Encoding: zstd). + Zstd, } diff --git a/src/Apify.Client/Resources/AbstractWebhookCollectionClient.cs b/src/Apify.Client/Resources/AbstractWebhookCollectionClient.cs index 91470ca..199f87e 100644 --- a/src/Apify.Client/Resources/AbstractWebhookCollectionClient.cs +++ b/src/Apify.Client/Resources/AbstractWebhookCollectionClient.cs @@ -8,10 +8,9 @@ namespace Apify.Client.Resources; /// -/// Shared read-only behavior for webhook collections. Both the account-wide collection -/// () and the read-only collections nested under an Actor or task -/// () can list webhooks; only the account-wide collection can -/// create them. +/// Read-only behavior shared by the webhook collections. The account-wide and the +/// collections nested under an Actor or task () can list webhooks; only the +/// account-wide one can create them. /// public abstract class AbstractWebhookCollectionClient { diff --git a/src/Apify.Client/Resources/ActorClient.cs b/src/Apify.Client/Resources/ActorClient.cs index 90023b9..0d1fa8f 100644 --- a/src/Apify.Client/Resources/ActorClient.cs +++ b/src/Apify.Client/Resources/ActorClient.cs @@ -9,12 +9,9 @@ namespace Apify.Client.Resources; /// -/// A client for a specific Actor. +/// A client for a specific Actor: get, update, delete, start, call, build, and access to its runs, builds, versions, +/// webhooks and server interface. /// -/// -/// It provides CRUD methods plus convenience helpers to start/call the Actor, build it, and access its -/// runs, builds, versions and webhooks. -/// public sealed class ActorClient { private readonly ApifyClient _root; @@ -43,7 +40,7 @@ internal ActorClient(ApifyClient root, HttpClientCore http, string baseUrl, stri return data is JsonObject obj ? new Actor(obj) : null; } - /// Updates the Actor with the given fields and returns the updated object. + /// Updates the Actor and returns the updated Actor. /// Any JSON-serializable set of fields to update. /// A token to cancel the request. public async Task UpdateAsync(object newFields, CancellationToken cancellationToken = default) @@ -55,10 +52,10 @@ public async Task UpdateAsync(object newFields, CancellationToken cancell /// A token to cancel the request. public Task DeleteAsync(CancellationToken cancellationToken = default) => _ctx.DeleteResourceAsync("", RequestTier.Short, cancellationToken); - /// Starts the Actor and returns immediately with the created run. + /// Starts the Actor and returns the created run without waiting. /// - /// A plain object or array serialized to JSON, raw bytes (byte[]) sent as-is (typically paired - /// with ), or null for no input. + /// byte[] is sent as it is (typically with ), anything else as JSON, + /// and null sends no input. /// /// Optional run-start options. /// A token to cancel the request. @@ -76,25 +73,21 @@ private async Task StartOnceAsync(object? input, ActorStartOptions opt var q = new QueryParams(); options.AppendTo(q); ResourceContext.EncodeInputBody(input, out var body, out var bodyBytes); - // Long, not the reference's base "medium": options.WaitForFinish can ask the server to hold the - // connection open for up to 60s, and this call does not clamp it to a shorter budget the way - // RunClient.GetAsync/BuildClient.GetAsync do, so the HTTP timeout must cover the full wait. + // Long: options.WaitForFinish can make the server hold the connection for up to 60s, and it is not clamped here. return new ActorRun(await _ctx.PostWithBodyAsync("runs", q, body, options.ContentTypeOrDefault(), RequestTier.Long, cancellationToken, bodyBytes).ConfigureAwait(false)); } - /// Starts the Actor and waits (client-side polling) for it to finish. - /// Any JSON-serializable value (or null for no input). + /// Starts the Actor and waits for it to finish by polling. + /// Any JSON-serializable value, or null for no input. /// - /// Optional run-start options. Time spent retrying - /// does not count toward , which only bounds the wait once the run starts. + /// Optional run-start options. Time spent retrying does not count + /// toward . /// /// Bounds the wait; null waits indefinitely. /// - /// If provided, the run's live log is redirected to this sink (one complete message per call) for the - /// duration of the wait, matching the reference client's log call option. Unlike the reference - /// client, which redirects the log by default when the option is omitted, null (the default) does - /// not redirect anything; pass to get the reference's default - /// redirection, with its <Actor name> runId:<id> -> prefix. + /// Redirects the run's live log to this sink, one complete message per call, while waiting. Unlike the reference + /// client, null redirects nothing; pass for the reference's default + /// redirection with the <Actor name> runId:<id> -> prefix. /// /// A token to cancel the request. public async Task CallAsync( @@ -108,10 +101,8 @@ public async Task CallAsync( return await _root.Run(run.Id ?? string.Empty).WaitForFinishWithLogAsync(waitSecs, log, cancellationToken).ConfigureAwait(false); } - /// Validates against the Actor's input schema and returns whether it is valid. - /// - /// A plain object or array serialized to JSON, raw bytes (byte[]) sent as-is, or null. - /// + /// Checks against the Actor's input schema and returns whether it is valid. + /// byte[] is sent as it is, anything else as JSON, null sends no input. /// Optional validation options. /// A token to cancel the request. public async Task ValidateInputAsync(object? input = null, ValidateInputOptions? options = null, CancellationToken cancellationToken = default) @@ -120,8 +111,7 @@ public async Task ValidateInputAsync(object? input = null, ValidateInputOp var q = new QueryParams(); options.AppendTo(q); ResourceContext.EncodeInputBody(input, out var body, out var bodyBytes); - // The validate-input endpoint returns a bare {"valid": } object, not the standard - // {"data": ...} envelope, so parse it without unwrapping. + // The endpoint returns a bare {"valid": bool} without the usual "data" envelope. var result = await _ctx.PostWithBodyNoEnvelopeAsync("validate-input", q, body, options.ContentTypeOrDefault(), RequestTier.Short, cancellationToken, bodyBytes).ConfigureAwait(false); return result is JsonObject obj && obj.TryGetPropertyValue("valid", out var valid) && valid?.GetValueKind() == System.Text.Json.JsonValueKind.True; @@ -140,8 +130,8 @@ public async Task BuildAsync(string versionNumber, ActorBuildOptions? opt } /// - /// Resolves the Actor's default build and returns a client for it. - /// optionally bounds how long (seconds) the API waits for the build to finish before responding. + /// Returns a client for the Actor's default build. bounds how long (seconds) the API + /// waits for the build to finish. /// /// Optional server-side wait in seconds. /// A token to cancel the request. @@ -149,13 +139,13 @@ public async Task DefaultBuildAsync(int? waitForFinish = null, Canc { var q = new QueryParams(); q.AddInt("waitForFinish", waitForFinish); - // Long: waitForFinish is sent unclamped, so the HTTP timeout must cover whatever the caller asks for. + // Long: waitForFinish is not clamped. var data = await _ctx.GetResourceRequiredAsync("builds/default", q, RequestTier.Long, cancellationToken).ConfigureAwait(false); var build = new Build(data); return new BuildClient(_http, _baseUrl, build.Id ?? string.Empty); } - /// Returns a client for the last run of this Actor, optionally filtered by status and/or origin. + /// Returns a client for the last run of this Actor, optionally filtered by status and origin. /// Optional last-run filters. public RunClient LastRun(LastRunOptions? options = null) { @@ -171,7 +161,7 @@ public RunClient LastRun(LastRunOptions? options = null) public RunCollectionClient Runs() => new(_http, _ctx.SubUrl(""), "runs"); /// A client for a specific version of this Actor. - /// The version identifier (e.g. 0.1). Must not be empty. + /// The version, such as 0.1. Must not be empty. public ActorVersionClient Version(string versionNumber) { if (versionNumber.Length == 0) @@ -186,22 +176,19 @@ public ActorVersionClient Version(string versionNumber) public ActorVersionCollectionClient Versions() => new(_http, _ctx.SubUrl("")); /// - /// A client for the server interface of this Actor. A Server Actor (formerly Actor Standby) runs a web server - /// that is called over HTTP without starting a run for each call. Every call returns the same client, so the - /// server URL it fetches is reused. + /// A client for the server interface of this Actor. A Server Actor (formerly Actor Standby) runs a web server that is + /// called over HTTP without starting a run per call. Every call returns the same client, which reuses the server URL. /// public ActorServerClient Server() => LazyInitializer.EnsureInitialized(ref _serverClient, () => new ActorServerClient(_http, _baseUrl, Id)); - /// A read-only client for this Actor's webhook collection (GET /v2/actors/{id}/webhooks). + /// A read-only client for this Actor's webhooks. public NestedWebhookCollectionClient Webhooks() => new(_http, _ctx.SubUrl("")); /// - /// Returns this client with every subsequent call's timeout set to , - /// overriding the tier default (see the "Timeout tiers" section of docs/README.md). Pass - /// for no timeout, matching the reference client's 'noTimeout'. A - /// value above (the overall budget) is capped at it — - /// raise itself to allow a longer per-call timeout. + /// Returns this client with the timeout of every subsequent call set to , overriding the + /// tier default (see "Timeout tiers" in docs/README.md). means no timeout. A value above + /// is capped at it. /// /// The timeout to use for every call made through this client. public ActorClient WithTimeout(TimeSpan timeout) diff --git a/src/Apify.Client/Resources/ActorCollectionClient.cs b/src/Apify.Client/Resources/ActorCollectionClient.cs index 1663c7a..0228e46 100644 --- a/src/Apify.Client/Resources/ActorCollectionClient.cs +++ b/src/Apify.Client/Resources/ActorCollectionClient.cs @@ -51,11 +51,9 @@ public async Task CreateAsync(object actor, CancellationToken cancellatio } /// - /// Returns this client with every subsequent call's timeout set to , - /// overriding the tier default (see the "Timeout tiers" section of docs/README.md). Pass - /// for no timeout, matching the reference client's 'noTimeout'. A - /// value above (the overall budget) is capped at it — - /// raise itself to allow a longer per-call timeout. + /// Returns this client with the timeout of every subsequent call set to , overriding the + /// tier default (see "Timeout tiers" in docs/README.md). means no timeout. A value above + /// is capped at it. /// /// The timeout to use for every call made through this client. public ActorCollectionClient WithTimeout(TimeSpan timeout) diff --git a/src/Apify.Client/Resources/ActorEnvVarClient.cs b/src/Apify.Client/Resources/ActorEnvVarClient.cs index b18e4a6..c6b783d 100644 --- a/src/Apify.Client/Resources/ActorEnvVarClient.cs +++ b/src/Apify.Client/Resources/ActorEnvVarClient.cs @@ -42,11 +42,9 @@ public async Task UpdateAsync(ActorEnvVar envVar, CancellationToken public Task DeleteAsync(CancellationToken cancellationToken = default) => _ctx.DeleteResourceAsync("", RequestTier.Short, cancellationToken); /// - /// Returns this client with every subsequent call's timeout set to , - /// overriding the tier default (see the "Timeout tiers" section of docs/README.md). Pass - /// for no timeout, matching the reference client's 'noTimeout'. A - /// value above (the overall budget) is capped at it — - /// raise itself to allow a longer per-call timeout. + /// Returns this client with the timeout of every subsequent call set to , overriding the + /// tier default (see "Timeout tiers" in docs/README.md). means no timeout. A value above + /// is capped at it. /// /// The timeout to use for every call made through this client. public ActorEnvVarClient WithTimeout(TimeSpan timeout) diff --git a/src/Apify.Client/Resources/ActorEnvVarCollectionClient.cs b/src/Apify.Client/Resources/ActorEnvVarCollectionClient.cs index 53378ca..65bca0d 100644 --- a/src/Apify.Client/Resources/ActorEnvVarCollectionClient.cs +++ b/src/Apify.Client/Resources/ActorEnvVarCollectionClient.cs @@ -38,9 +38,8 @@ public async Task CreateAsync(ActorEnvVar envVar, CancellationToken } /// - /// Lazily iterates over the version's environment variables. The env-var endpoint returns the whole - /// list in a single page, so this yields that page's items; it exists for API parity with the other - /// collection iterators and the reference client. + /// Iterates the version's environment variables. The endpoint returns the whole list in one page, so this yields that + /// page's items; it exists for parity with the other collection iterators and the reference client. /// /// A token to cancel the iteration. public async IAsyncEnumerable IterateAsync([EnumeratorCancellation] CancellationToken cancellationToken = default) @@ -54,11 +53,9 @@ public async IAsyncEnumerable IterateAsync([EnumeratorCancellation] /// - /// Returns this client with every subsequent call's timeout set to , - /// overriding the tier default (see the "Timeout tiers" section of docs/README.md). Pass - /// for no timeout, matching the reference client's 'noTimeout'. A - /// value above (the overall budget) is capped at it — - /// raise itself to allow a longer per-call timeout. + /// Returns this client with the timeout of every subsequent call set to , overriding the + /// tier default (see "Timeout tiers" in docs/README.md). means no timeout. A value above + /// is capped at it. /// /// The timeout to use for every call made through this client. public ActorEnvVarCollectionClient WithTimeout(TimeSpan timeout) diff --git a/src/Apify.Client/Resources/ActorServerClient.cs b/src/Apify.Client/Resources/ActorServerClient.cs index 2c0dd1c..d323af9 100644 --- a/src/Apify.Client/Resources/ActorServerClient.cs +++ b/src/Apify.Client/Resources/ActorServerClient.cs @@ -19,16 +19,19 @@ namespace Apify.Client.Resources; /// A client for the server interface of a specific Actor, obtained with . /// /// -/// A Server Actor (formerly Actor Standby) runs a web server that is called over HTTP without starting a run for -/// each call. This client sends requests to that server, and returns its URL and its OpenAPI specification. See -/// . +/// A Server Actor (formerly Actor Standby) runs a web server that is called over HTTP without starting a run for each +/// call. This client sends requests to that server and returns its URL and OpenAPI specification. See +/// . /// public sealed class ActorServerClient { - /// The header that carries the API token to an Actor's server. The platform removes it before the request reaches the Actor. + /// The header that carries the API token to an Actor's server; the platform removes it before the request reaches the Actor. private const string ServerAuthorizationHeader = "X-Apify-Authorization"; - /// The build tag whose build serves the server interface when actorStandby.build is not set. + /// The header that identifies a request to a multi-tenant Server Actor. + private const string ParentRequestIdHeader = "X-Actor-Request-ID"; + + /// The build tag that serves the server interface when actorStandby.build is not set. private const string DefaultBuildTag = "latest"; private const string DefaultRequestPath = "/"; @@ -36,25 +39,21 @@ public sealed class ActorServerClient private const string TextContentType = "text/plain; charset=utf-8"; private const string BinaryContentType = "application/octet-stream"; - /// The format of a date in a query parameter: ISO 8601 in UTC with milliseconds, as the reference client sends it. + /// The format of a date in a query parameter: ISO 8601 in UTC with milliseconds, as in the reference client. private const string QueryTimestampFormat = "yyyy-MM-dd'T'HH:mm:ss.fff'Z'"; /// The separator of the values of a list in a query parameter. private const string QueryListSeparator = ","; - /// The Service Unavailable status the platform answers with when it cannot pass a request to a server run. + /// The status the platform answers with when it cannot pass a request to a server run. private const int ServiceUnavailableStatus = 503; - /// - /// Bounds the build number matching so that a pathological input cannot stall the call; the pattern is simple, - /// so a second is generous. - /// + /// Bounds the build number matching so that a pathological input cannot stall the call. private static readonly TimeSpan BuildNumberMatchTimeout = TimeSpan.FromSeconds(1); /// - /// Statuses a server request is retried on. The platform answers with them when it cannot pass a request to a - /// server run, so a retry usually does not run the Actor's code again. An Actor that returns one itself gets - /// the request again. + /// Statuses a server request is retried on. The platform answers with them when it cannot pass a request to a server + /// run, so a retry usually does not run the Actor's code again. An Actor that returns one itself gets the request again. /// private static readonly int[] ServerRetryStatusCodes = { HttpClientCore.RateLimitExceeded, ServiceUnavailableStatus }; @@ -78,13 +77,12 @@ internal ActorServerClient(HttpClientCore http, string baseUrl, string id) public string Id { get; } /// - /// Returns the URL of the Actor's server, to call it with another HTTP client, for example one generated from - /// the server's OpenAPI specification (see ). + /// Returns the URL of the Actor's server, to call it with another HTTP client, for example one generated from the + /// server's OpenAPI specification (see ). /// /// - /// The URL is the Actor's standbyUrl. The first call of this method, or - /// fetches it from the API and this client reuses it afterwards; concurrent first - /// calls may each fetch it. + /// The URL is the Actor's standbyUrl. The first call of this method, , or + /// fetches it and the client reuses it afterwards; concurrent first calls may each fetch it. /// /// A token to cancel the request. /// The Actor is not a Server Actor. @@ -93,14 +91,13 @@ public async Task GetUrlAsync(CancellationToken cancellationToken = defa => _serverUrl ?? (await GetServerActorAsync(cancellationToken).ConfigureAwait(false)).StandbyUrl!; /// - /// Returns the OpenAPI specification of the Actor's server, to generate an HTTP client for it and point that - /// client at the URL returned by ; null if the Actor's definition does not - /// include one. + /// Returns the OpenAPI specification of the Actor's server, to generate an HTTP client for it; null if the + /// Actor's definition has none. /// /// - /// The specification is the web server schema of the Actor's definition, taken from the build that serves the - /// server interface: the build set in actorStandby.build (a tag or a build number), or latest if - /// none is set. See . + /// The specification is the web server schema of the build that serves the server interface: actorStandby.build + /// (a tag or a build number), or latest. See + /// . /// /// A token to cancel the requests. /// The Actor is not a Server Actor, or the build that serves its server interface does not exist. @@ -114,7 +111,7 @@ public async Task GetUrlAsync(CancellationToken cancellationToken = defa var buildId = TaggedBuildId(actor, buildRef); if (buildId is null && ParseBuildNumber(buildRef) is { } target) { - // Not a tag, so look `buildRef` up as a build number, which the API cannot resolve directly. + // Not a tag, so look it up as a build number, which the API cannot resolve directly. buildId = await FindBuildIdByNumberAsync(buildRef, target, cancellationToken).ConfigureAwait(false); } @@ -127,31 +124,26 @@ public async Task GetUrlAsync(CancellationToken cancellationToken = defa return build.ActorDefinition?["webServerSchema"] as JsonObject; } - /// - /// Sends an HTTP request to the Actor's server and returns its response, whatever its status. - /// + /// Sends an HTTP request to the Actor's server and returns its response, whatever its status. /// /// - /// The request goes to the URL returned by and carries the client's token in the - /// X-Apify-Authorization header, which the platform consumes, so it never reaches the Actor's code. - /// Headers in , Authorization included, reach the Actor's - /// server as they are. The first request to an idle Actor waits for the platform to start a server run, which - /// is why the timeout defaults to the Long tier. + /// The request goes to the URL from with the client's token in the + /// X-Apify-Authorization header, which the platform consumes, so the Actor's code never sees it. Headers in + /// , Authorization included, reach the Actor as they are. The + /// first request to an idle Actor waits for a server run to start, so the timeout defaults to the Long tier. /// /// - /// A rate limit (429) or Service Unavailable (503) response is retried with the client's retry settings, and - /// the last one is returned once the retries run out. The Actor's code can return these statuses too, and - /// then it gets the request again. Other statuses are not retried, since they may come from the Actor's code, - /// and neither are network errors and timeouts, since the Actor may have already handled the request. A - /// redirect is returned as it is, since following it would send the token to a host the Actor's code picked. - /// Request bodies are sent uncompressed unless is set. + /// A 429 or 503 response is retried with the client's retry settings, and the last one is returned when the retries + /// run out. Other statuses are not retried, since they may come from the Actor's code, and neither are network errors + /// and timeouts, since the Actor may have handled the request. A redirect is returned as it is, since following it + /// would send the token to a host the Actor's code picked. Bodies are sent uncompressed unless + /// is set. /// /// /// - /// The path on the Actor's server, for example /search. It is always resolved against the server URL, - /// so the request, and the token with it, cannot be sent to another host. Pass the query in - /// . A fragment (#...) is dropped, since it is never sent - /// to the server. + /// The path on the Actor's server, for example /search. It is resolved against the server URL, so the request + /// and the token cannot go to another host. Pass the query in . A fragment + /// (#...) is dropped. /// /// Optional request settings. /// A token to cancel the request. @@ -202,7 +194,7 @@ public async Task RequestAsync( options.ContentType ?? FindHeader(options.Headers, HttpClientCore.ContentTypeHeader) ?? DefaultContentType(options.Body), options.Timeout, bodyBytes: bodyBytes, - extraHeaders: options.Headers, + extraHeaders: WithParentRequestId(options.Headers, options.ParentRequestId), tier: RequestTier.Long, explicitContentEncoding: FindHeader(options.Headers, HttpClientCore.ContentEncodingHeader), policy: policy, @@ -211,12 +203,10 @@ public async Task RequestAsync( } /// - /// Returns this client with the timeout of the API calls it makes (the Actor and build lookups of - /// and ) set to , - /// overriding the tier default (see the "Timeout tiers" section of docs/README.md). Pass - /// for no timeout. A value above - /// is capped at it. It does not affect , which takes its timeout from - /// . + /// Returns this client with the timeout of its API calls (the Actor and build lookups) set to + /// , overriding the tier default (see "Timeout tiers" in docs/README.md). + /// means no timeout. A value above is capped at + /// it. uses instead. /// /// The timeout to use for the API calls made through this client. public ActorServerClient WithTimeout(TimeSpan timeout) @@ -250,8 +240,8 @@ private static void AddQueryValue(QueryParams query, string key, object? value) } /// - /// Converts a query value to text: dates to ISO 8601 in UTC, booleans to 1/0, lists to their - /// comma-joined values and other values to their invariant text; null stays null and is left out. + /// Converts a query value to text: dates to ISO 8601 in UTC, booleans to 1/0, lists to comma-joined + /// values and others to their invariant text. null is left out. /// private static string? FormatQueryValue(object? value) => value switch { @@ -265,7 +255,7 @@ private static void AddQueryValue(QueryParams query, string key, object? value) _ => value.ToString(), }; - /// A date as UTC; a date of unspecified kind is taken as UTC too, so it is sent the same on every machine. + /// A date as UTC; a date of unspecified kind is taken as UTC, so it is sent the same on every machine. private static DateTime ToUtc(DateTime time) => time.Kind == DateTimeKind.Unspecified ? DateTime.SpecifyKind(time, DateTimeKind.Utc) : time.ToUniversalTime(); @@ -276,6 +266,26 @@ private static DateTime ToUtc(DateTime time) _ => ResourceContext.ContentTypeJson, }; + private static IReadOnlyDictionary? WithParentRequestId(IReadOnlyDictionary? headers, string? parentRequestId) + { + if (parentRequestId is null) + { + return headers; + } + + var merged = new Dictionary(StringComparer.OrdinalIgnoreCase); + if (headers is not null) + { + foreach (var header in headers) + { + merged[header.Key] = header.Value; + } + } + + merged[ParentRequestIdHeader] = parentRequestId; + return merged; + } + private static string? FindHeader(IReadOnlyDictionary? headers, string name) { if (headers is null) @@ -320,8 +330,7 @@ private async Task GetServerActorAsync(CancellationToken cancellationToke builds.WithTimeout(timeout); } - // Builds are listed newest first and the sequence numbers of a version grow over time, so meeting an older - // build of the same version means the target does not exist. + // Builds are listed newest first and sequence numbers grow, so an older build of the same version means the target does not exist. await foreach (var build in builds.IterateAsync(new ListOptions { Desc = true }, cancellationToken).ConfigureAwait(false)) { if (build.BuildNumber == buildNumber) @@ -350,10 +359,7 @@ private BuildClient BuildClientFor(string buildId) return client; } - /// - /// Splits a build number like 0.1.23 into its parts, or returns null for anything else, including - /// numbers too large to be a real build number. - /// + /// Splits a build number such as 0.1.23 into its parts; null for anything else. private static (long Major, long Minor, long Sequence)? ParseBuildNumber(string? buildNumber) { var match = BuildNumberPattern.Match(buildNumber ?? string.Empty); diff --git a/src/Apify.Client/Resources/ActorServerResponse.cs b/src/Apify.Client/Resources/ActorServerResponse.cs index 2db329c..f55befa 100644 --- a/src/Apify.Client/Resources/ActorServerResponse.cs +++ b/src/Apify.Client/Resources/ActorServerResponse.cs @@ -49,9 +49,8 @@ public Task ReadAsStringAsync(CancellationToken cancellationToken = defa => _response.Content.ReadAsStringAsync(cancellationToken); /// - /// Reads the response body as a stream. With - /// the body is not read yet and arrives as it is received; the stream is valid until this response is - /// disposed. + /// Reads the response body as a stream. With the body + /// arrives as it is received. The stream is valid until this response is disposed. /// /// A token to cancel the read. public Task ReadAsStreamAsync(CancellationToken cancellationToken = default) diff --git a/src/Apify.Client/Resources/ActorVersionClient.cs b/src/Apify.Client/Resources/ActorVersionClient.cs index 2d7f8f0..e74ff1c 100644 --- a/src/Apify.Client/Resources/ActorVersionClient.cs +++ b/src/Apify.Client/Resources/ActorVersionClient.cs @@ -60,11 +60,9 @@ public ActorEnvVarClient EnvVar(string name) public ActorEnvVarCollectionClient EnvVars() => new(_http, _versionUrl); /// - /// Returns this client with every subsequent call's timeout set to , - /// overriding the tier default (see the "Timeout tiers" section of docs/README.md). Pass - /// for no timeout, matching the reference client's 'noTimeout'. A - /// value above (the overall budget) is capped at it — - /// raise itself to allow a longer per-call timeout. + /// Returns this client with the timeout of every subsequent call set to , overriding the + /// tier default (see "Timeout tiers" in docs/README.md). means no timeout. A value above + /// is capped at it. /// /// The timeout to use for every call made through this client. public ActorVersionClient WithTimeout(TimeSpan timeout) diff --git a/src/Apify.Client/Resources/ActorVersionCollectionClient.cs b/src/Apify.Client/Resources/ActorVersionCollectionClient.cs index 6f13162..2482200 100644 --- a/src/Apify.Client/Resources/ActorVersionCollectionClient.cs +++ b/src/Apify.Client/Resources/ActorVersionCollectionClient.cs @@ -18,10 +18,7 @@ internal ActorVersionCollectionClient(HttpClientCore http, string actorUrl) _ctx = ResourceContext.Collection(http, actorUrl, "versions"); } - /// - /// Lists the Actor's versions. The endpoint returns every version in one response; it does not support - /// pagination, so unlike most collection clients this takes no filtering/paging options. - /// + /// Lists the Actor's versions. The endpoint returns all of them in one response, so this takes no paging options. /// A token to cancel the request. public Task> ListAsync(CancellationToken cancellationToken = default) { @@ -37,9 +34,8 @@ public async Task CreateAsync(object version, CancellationToken ca } /// - /// Iterates over the Actor's versions. The endpoint returns the whole list in a single page, so this - /// yields that page's items; it exists for API parity with the other collection iterators and the - /// reference client. + /// Iterates the Actor's versions. The endpoint returns the whole list in one page, so this yields that page's items; it + /// exists for parity with the other collection iterators and the reference client. /// /// A token to cancel the iteration. public async IAsyncEnumerable IterateAsync([EnumeratorCancellation] CancellationToken cancellationToken = default) @@ -52,11 +48,9 @@ public async IAsyncEnumerable IterateAsync([EnumeratorCancellation } /// - /// Returns this client with every subsequent call's timeout set to , - /// overriding the tier default (see the "Timeout tiers" section of docs/README.md). Pass - /// for no timeout, matching the reference client's 'noTimeout'. A - /// value above (the overall budget) is capped at it — - /// raise itself to allow a longer per-call timeout. + /// Returns this client with the timeout of every subsequent call set to , overriding the + /// tier default (see "Timeout tiers" in docs/README.md). means no timeout. A value above + /// is capped at it. /// /// The timeout to use for every call made through this client. public ActorVersionCollectionClient WithTimeout(TimeSpan timeout) diff --git a/src/Apify.Client/Resources/BuildClient.cs b/src/Apify.Client/Resources/BuildClient.cs index 33eacf7..d67b29e 100644 --- a/src/Apify.Client/Resources/BuildClient.cs +++ b/src/Apify.Client/Resources/BuildClient.cs @@ -20,18 +20,17 @@ internal BuildClient(HttpClientCore http, string baseUrl, string id) } /// - /// Fetches the build, optionally asking the API to wait up to - /// seconds (max 60) for the build to finish before responding. Returns null if it does not exist. + /// Fetches the build, optionally asking the API to wait up to seconds (at most + /// 60) for it to finish. Returns null if it does not exist. /// /// Optional server-side wait in seconds. /// A token to cancel the request. public async Task GetAsync(int? waitForFinishSecs = null, CancellationToken cancellationToken = default) { var q = new QueryParams(); - // Clamp to the client's per-request timeout so a short custom timeout doesn't abort the call. + // Clamped to the request timeout, so a short custom timeout does not abort the call. q.AddInt("waitForFinish", _ctx.ClampServerWait(waitForFinishSecs)); - // Long: the clamped waitForFinish can hold the connection open close to the overall budget, so the - // HTTP timeout for this call must cover it (matches the reference's short-base-extended-for-wait). + // Long tier: the server may hold the connection for most of the wait. var data = await _ctx.GetResourceAsync("", q, RequestTier.Long, cancellationToken).ConfigureAwait(false); return data is JsonObject obj ? new Build(obj) : null; } @@ -48,8 +47,8 @@ public async Task AbortAsync(CancellationToken cancellationToken = defaul public Task DeleteAsync(CancellationToken cancellationToken = default) => _ctx.DeleteResourceAsync("", RequestTier.Short, cancellationToken); /// - /// Polls until the build reaches a terminal state or elapses (null - /// waits indefinitely). Returns the latest build. + /// Polls until the build reaches a terminal state or elapses (null waits + /// indefinitely) and returns the latest build. /// /// The wait budget in seconds, or null to wait indefinitely. /// A token to cancel the wait. @@ -70,11 +69,9 @@ public async Task WaitForFinishAsync(int? waitSecs = null, CancellationTo public LogClient Log() => LogClient.Nested(_http, _ctx.SubUrl(""), timeout: _ctx.RequestTimeout); /// - /// Returns this client with every subsequent call's timeout set to , - /// overriding the tier default (see the "Timeout tiers" section of docs/README.md). Pass - /// for no timeout, matching the reference client's 'noTimeout'. A - /// value above (the overall budget) is capped at it — - /// raise itself to allow a longer per-call timeout. + /// Returns this client with the timeout of every subsequent call set to , overriding the + /// tier default (see "Timeout tiers" in docs/README.md). means no timeout. A value above + /// is capped at it. /// /// The timeout to use for every call made through this client. public BuildClient WithTimeout(TimeSpan timeout) diff --git a/src/Apify.Client/Resources/BuildCollectionClient.cs b/src/Apify.Client/Resources/BuildCollectionClient.cs index 14a9074..b4592c3 100644 --- a/src/Apify.Client/Resources/BuildCollectionClient.cs +++ b/src/Apify.Client/Resources/BuildCollectionClient.cs @@ -45,11 +45,9 @@ public IAsyncEnumerable IterateAsync(ListOptions? options = null, Cancell /// - /// Returns this client with every subsequent call's timeout set to , - /// overriding the tier default (see the "Timeout tiers" section of docs/README.md). Pass - /// for no timeout, matching the reference client's 'noTimeout'. A - /// value above (the overall budget) is capped at it — - /// raise itself to allow a longer per-call timeout. + /// Returns this client with the timeout of every subsequent call set to , overriding the + /// tier default (see "Timeout tiers" in docs/README.md). means no timeout. A value above + /// is capped at it. /// /// The timeout to use for every call made through this client. public BuildCollectionClient WithTimeout(TimeSpan timeout) diff --git a/src/Apify.Client/Resources/DatasetClient.cs b/src/Apify.Client/Resources/DatasetClient.cs index 57eab05..890444c 100644 --- a/src/Apify.Client/Resources/DatasetClient.cs +++ b/src/Apify.Client/Resources/DatasetClient.cs @@ -15,7 +15,7 @@ namespace Apify.Client.Resources; -/// A client for a specific dataset (and run-nested variants). +/// A client for a specific dataset. public sealed class DatasetClient { private readonly HttpClientCore _http; @@ -47,7 +47,7 @@ internal DatasetClient WithPublicBase(string publicBaseUrl) return data is JsonObject obj ? new Dataset(obj) : null; } - /// Updates the dataset metadata (e.g. name, title) and returns the updated object. + /// Updates the dataset metadata, such as its name or title, and returns the updated dataset. /// Any JSON-serializable set of fields to update. /// A token to cancel the request. public async Task UpdateAsync(object newFields, CancellationToken cancellationToken = default) @@ -59,15 +59,12 @@ public async Task UpdateAsync(object newFields, CancellationToken cance /// A token to cancel the request. public Task DeleteAsync(CancellationToken cancellationToken = default) => _ctx.DeleteResourceAsync("", RequestTier.Short, cancellationToken); - /// - /// Lists items from the dataset, each decoded to a (objects become - /// ). - /// + /// Lists items from the dataset, each decoded to a . /// - /// The dataset items endpoint returns a bare JSON array (not a data envelope) and reports pagination via - /// X-Apify-Pagination-* headers, surfaced in the returned page. + /// The endpoint returns a bare JSON array and reports pagination in X-Apify-Pagination-* headers, which end up + /// in the returned page. /// - /// Optional item filtering/projection and pagination. + /// Optional item filtering, projection, and pagination. /// A token to cancel the request. public async Task> ListItemsAsync(DatasetListItemsOptions? options = null, CancellationToken cancellationToken = default) { @@ -77,23 +74,19 @@ public async Task UpdateAsync(object newFields, CancellationToken cance return await FetchItemsPageAsync(q, options.Desc ?? false, cancellationToken).ConfigureAwait(false); } - /// - /// Lazily iterates over all items of the dataset across pages, fetching each page on demand. Mirrors - /// the reference client's auto-paging listItems iterator. - /// + /// Lazily iterates all items of the dataset across pages, fetching each page on demand. /// - /// The offset for the next page, and whether iteration continues, follow the number of rows the API - /// scanned (X-Apify-Pagination-Count) rather than the number of items a page returned: the - /// // - /// filters apply after offset/limit, so a - /// page can scan up to the requested limit of rows while returning fewer of them, or none at all, and - /// can return more items than rows scanned. - /// therefore caps the rows scanned (0 or null - /// for no cap). The header can lag behind items that were just pushed, so the larger of it and the - /// returned item count is used, capped at the page's requested limit; see . + /// Pagination follows the rows the API scanned (X-Apify-Pagination-Count), not the items a page returned. The + /// , and + /// filters apply after offset and limit, so a page can + /// scan its full limit and return fewer items, and can return more items + /// than rows. therefore caps the rows scanned (0 or null for no + /// cap). The header lags behind fresh pushes, so the larger of it and the item count is used, capped at the page limit. /// - /// Optional item filtering/projection; Offset/Limit bound where - /// iteration starts and the rows scanned, and ChunkSize sets the page size. + /// + /// Optional filtering and projection. Offset and Limit bound where iteration starts and the rows scanned, + /// and ChunkSize sets the page size. + /// /// A token to cancel the iteration. public IAsyncEnumerable IterateItemsAsync( DatasetListItemsOptions? options = null, @@ -116,7 +109,7 @@ public async Task UpdateAsync(object newFields, CancellationToken cance cancellationToken); } - /// Fetches a single page of items for the given options (see the query overload). + /// Fetches one page of items. internal Task> FetchItemsPageAsync(DatasetListItemsOptions options, CancellationToken cancellationToken) { var q = new QueryParams(); @@ -125,11 +118,9 @@ public async Task UpdateAsync(object newFields, CancellationToken cance } /// - /// Fetches a single page of dataset items. The endpoint returns a bare JSON array with pagination in - /// X-Apify-Pagination-* response headers. The page's is the - /// number of rows the API scanned to produce it (X-Apify-Pagination-Count), which the offset - /// iterator paginates by (see ). The header is derived from the dataset's - /// item count, which lags a fresh push, so the item count is used when it is larger or the header is absent. + /// Fetches one page of items. The page's is the number of rows the API scanned + /// (X-Apify-Pagination-Count), or the item count when that is larger or the header is missing, since the header + /// lags fresh pushes. /// private async Task> FetchItemsPageAsync(QueryParams q, bool desc, CancellationToken cancellationToken) { @@ -152,11 +143,8 @@ public async Task UpdateAsync(object newFields, CancellationToken cance } /// - /// Downloads dataset items serialized in the given format, returning the raw bytes. Unlike - /// (parsed items), this returns the items already serialized to JSON, CSV, - /// XLSX, XML, RSS or HTML — useful for exporting. Bytes (not a decoded string) are returned so binary - /// formats such as (a ZIP-based export) are not corrupted; decode - /// text formats yourself, e.g. System.Text.Encoding.UTF8.GetString(bytes). + /// Downloads the items serialized in the given format (JSON, CSV, XLSX, XML, RSS, or HTML) as raw bytes, which keeps + /// binary formats such as intact. Decode text formats yourself. /// /// The output format. /// Optional format-specific and filtering options. @@ -172,15 +160,11 @@ public async Task DownloadItemsAsync(DownloadItemsFormat format, Dataset } /// - /// Downloads dataset items serialized in the given format as a stream, so a large export is processed - /// incrementally without holding it all in memory. Takes the same arguments as - /// ; only how the response body is consumed differs. + /// Downloads the items serialized in the given format as a stream, so a large export need not fit in memory. Takes the + /// same arguments as . /// /// - /// Unlike , this bypasses the buffered/retrying transport so the body - /// can be read incrementally as it arrives (a single attempt, not retried, matching - /// ). The caller must dispose the returned stream; disposing it also - /// releases the underlying HTTP response/connection. + /// A single attempt, not retried, like . Dispose the stream to release the connection. /// /// The output format. /// Optional format-specific and filtering options. @@ -206,11 +190,9 @@ public async Task StreamItemsAsync(DownloadItemsFormat format, DatasetDo /// Pushes one or more items to the dataset. /// - /// The API only accepts a JSON object or an array of JSON objects, so any other shape (a bare number, a - /// plain string, an array holding anything but objects) is rejected before a request is sent. A - /// is treated as already-serialized JSON and sent as it is, so it has to hold such an - /// object or array. A push that times out is not retried, because the API may already have stored the - /// items and a retry would store them twice; the is thrown instead. + /// The API accepts only a JSON object or an array of objects, so any other shape is rejected before a request is sent. + /// A is sent as already serialized JSON. A push that times out is not retried, because the API + /// may have stored the items already; an is thrown instead. /// /// A single item, an array of items, or the JSON serialization of either. /// A token to cancel the request. @@ -233,8 +215,8 @@ public async Task PushItemsAsync(object items, CancellationToken cancellationTok } /// - /// Serializes the items to UTF-8 JSON once, requiring an object or an array of objects. The shape is - /// checked by reading the tokens of the serialized bytes, so no document tree is built. + /// Serializes the items to UTF-8 JSON once and checks that they are an object or an array of objects, by reading the + /// tokens instead of building a document tree. /// private static byte[] SerializeItems(object items) { @@ -278,30 +260,22 @@ private static bool IsObjectOrArrayOfObjects(ReadOnlySpan json) return true; } - /// Returns statistical information about the dataset. - /// - /// Throws if the dataset does not exist (no longer resolves to null): the statistics endpoint has - /// no meaning apart from its parent dataset, so a missing dataset is reported as an error rather than an - /// ambiguous empty result, matching the reference client. - /// + /// Returns statistics about the dataset. + /// Throws if the dataset does not exist, as in the reference client. /// A token to cancel the request. public async Task GetStatisticsAsync(CancellationToken cancellationToken = default) { return await _ctx.GetDataObjectRequiredAsync("statistics", new QueryParams(), RequestTier.Short, cancellationToken).ConfigureAwait(false); } - /// - /// Builds a public URL for downloading this dataset's items. - /// + /// Builds a public URL for downloading the dataset's items. /// - /// It fetches the dataset, and if the dataset exposes a URL-signing secret key (i.e. it is private), - /// appends an HMAC-SHA256 signature so the URL grants access without an API token. - /// optionally bounds the validity of a signed URL (null for - /// non-expiring). The URL is built from the configured public base URL. + /// For a private dataset (one with a URL-signing secret key), the URL carries an HMAC-SHA256 signature, so it works + /// without an API token. /// - /// Optional item filtering/projection options forwarded into the URL. - /// Optional expiry in seconds for a signed URL; must be positive (omit it for a URL that never expires). - /// Output format served by the URL (null defaults to ). + /// Optional item filtering and projection, forwarded into the URL. + /// Expiry of a signed URL in seconds; must be positive. Omit it for a URL that never expires. + /// The format served by the URL (null for ). /// A token to cancel the request. public async Task CreateItemsPublicUrlAsync( DatasetListItemsOptions? options = null, @@ -349,11 +323,9 @@ private static long HeaderInt(HttpResponseMessage response, string name, long fa } /// - /// Returns this client with every subsequent call's timeout set to , - /// overriding the tier default (see the "Timeout tiers" section of docs/README.md). Pass - /// for no timeout, matching the reference client's 'noTimeout'. A - /// value above (the overall budget) is capped at it — - /// raise itself to allow a longer per-call timeout. + /// Returns this client with the timeout of every subsequent call set to , overriding the + /// tier default (see "Timeout tiers" in docs/README.md). means no timeout. A value above + /// is capped at it. /// /// The timeout to use for every call made through this client. public DatasetClient WithTimeout(TimeSpan timeout) diff --git a/src/Apify.Client/Resources/DatasetCollectionClient.cs b/src/Apify.Client/Resources/DatasetCollectionClient.cs index c723d45..4ad2d01 100644 --- a/src/Apify.Client/Resources/DatasetCollectionClient.cs +++ b/src/Apify.Client/Resources/DatasetCollectionClient.cs @@ -30,9 +30,8 @@ public Task> ListAsync(StorageListOptions? options = nul } /// - /// Gets the dataset with the given name, creating it if it does not exist. An empty/null name - /// creates a new unnamed dataset. An optional is sent when creating the - /// dataset, mirroring the reference client's getOrCreate(name, { schema }). + /// Gets the dataset with the given name, creating it if it does not exist. An empty or null name creates an + /// unnamed dataset. An optional is sent on creation. /// /// The dataset name, or null for a new unnamed dataset. /// An optional dataset schema to send on creation. @@ -56,11 +55,9 @@ public IAsyncEnumerable IterateAsync(StorageListOptions? options = null /// - /// Returns this client with every subsequent call's timeout set to , - /// overriding the tier default (see the "Timeout tiers" section of docs/README.md). Pass - /// for no timeout, matching the reference client's 'noTimeout'. A - /// value above (the overall budget) is capped at it — - /// raise itself to allow a longer per-call timeout. + /// Returns this client with the timeout of every subsequent call set to , overriding the + /// tier default (see "Timeout tiers" in docs/README.md). means no timeout. A value above + /// is capped at it. /// /// The timeout to use for every call made through this client. public DatasetCollectionClient WithTimeout(TimeSpan timeout) diff --git a/src/Apify.Client/Resources/KeyValueStoreClient.cs b/src/Apify.Client/Resources/KeyValueStoreClient.cs index b5f8d3a..dbb32cd 100644 --- a/src/Apify.Client/Resources/KeyValueStoreClient.cs +++ b/src/Apify.Client/Resources/KeyValueStoreClient.cs @@ -11,7 +11,7 @@ namespace Apify.Client.Resources; -/// A client for a specific key-value store (and run-nested variants). +/// A client for a specific key-value store. public sealed class KeyValueStoreClient { private readonly HttpClientCore _http; @@ -43,7 +43,7 @@ internal KeyValueStoreClient WithPublicBase(string publicBaseUrl) return data is System.Text.Json.Nodes.JsonObject obj ? new KeyValueStore(obj) : null; } - /// Updates the store metadata (e.g. name) and returns the updated object. + /// Updates the store metadata, such as its name, and returns the updated store. /// Any JSON-serializable set of fields to update. /// A token to cancel the request. public async Task UpdateAsync(object newFields, CancellationToken cancellationToken = default) @@ -65,23 +65,18 @@ public async Task ListKeysAsync(ListKeysOptions? options return KeyValueStoreKeysPage.FromData(await _ctx.GetResourceRequiredAsync("keys", q, RequestTier.Medium, cancellationToken).ConfigureAwait(false)); } - /// - /// Lazily iterates over the store's keys across pages, fetching each page on demand. Mirrors the - /// reference client's chunked listKeys() iteration. - /// + /// Lazily iterates the store's keys across pages, fetching each page on demand. /// - /// With no options it fetches pages of up to keys - /// until the store is exhausted. Limit caps the total number of keys yielded across all pages — - /// 0 yields none, without issuing any request (the API itself reads a literal limit=0 as - /// unbounded, not zero, so this is checked before building the request rather than left to the API), and - /// must not be negative. ChunkSize caps the page size (never sent to the API itself) and must - /// be positive (MaxPageLimit is its older name), and ExclusiveStartKey chooses the starting point (first page only). + /// Without options it fetches pages of up to keys until the store + /// is exhausted. Limit caps the total number of keys; 0 yields none without a request, and it must not be + /// negative. ChunkSize (older name MaxPageLimit) is the page size and must be positive. + /// ExclusiveStartKey chooses the starting point. /// /// Optional iteration options. /// A token to cancel the iteration. /// - /// or is zero or negative, or - /// is negative. + /// or is zero or negative, or + /// is negative. /// public async IAsyncEnumerable IterateKeysAsync( IterateKeysOptions? options = null, @@ -103,9 +98,8 @@ public async IAsyncEnumerable IterateKeysAsync( while (true) { - // Checked before building the request, not only after: the API reads a literal `limit=0` as - // unset (unbounded) rather than "zero", so a Limit of 0 (or one already exhausted by a prior - // page) must stop here instead of reaching the wire as 0 and fetching a full page regardless. + // Checked before building the request: the API reads `limit=0` as unbounded, so a Limit of 0 (or one already + // reached) must stop here. if (limit is not null && iterated >= limit.Value) { yield break; @@ -135,13 +129,8 @@ public async IAsyncEnumerable IterateKeysAsync( iterated += page.Items.Count; - // Continue only while there is a next cursor to follow — mirrors the reference client - // (apify-client-js `key_value_store.ts` listKeys iteration), which continues on - // `nextExclusiveStartKey != null` rather than on `isTruncated` alone. The API answers with - // `null` on the last page and omits the field entirely for a listing that was not truncated, - // but `isTruncated` alone is not a safe continuation signal: if it were ever `true` alongside a - // null/absent cursor, looping on it would re-fetch the same first page forever (no - // `exclusiveStartKey` reaches the wire for a null cursor), yielding duplicate keys. + // Continue only while there is a next cursor, as in the reference client. `isTruncated` alone is not safe: with a + // null cursor no `exclusiveStartKey` would be sent, and the first page would be fetched again forever. if (page.NextExclusiveStartKey is null || (limit is not null && iterated >= limit.Value)) { yield break; @@ -158,14 +147,12 @@ public Task RecordExistsAsync(string key, CancellationToken cancellationTo => _ctx.HeadExistsAsync("records/" + ResourceContext.EncodePathSegment(key, nameof(key)), new QueryParams(), RequestTier.Long, cancellationToken); /// - /// Fetches a record by key, or null if it does not exist. Like the reference client, it requests - /// the record as an attachment so the API returns the raw bytes directly. + /// Fetches a record by key, or null if it does not exist. As in the reference client, the record is requested + /// as an attachment, so the API returns the raw bytes. /// /// - /// The body is never parsed: holds the raw bytes whatever the - /// content type, so a truncated JSON body cannot fail here, and the caller decodes it according to - /// . (The reference client parses JSON, text and XML records - /// by content type; its buffer option returns the raw bytes like this method does.) + /// The body is never parsed: holds the raw bytes whatever the content type, so + /// decode them according to . /// /// The record key. /// Optional fetch options. @@ -179,7 +166,7 @@ public Task RecordExistsAsync(string key, CancellationToken cancellationTo try { using var response = await _http.CallAsync(HttpMethod.Get, url, timeout: _ctx.RequestTimeout, tier: RequestTier.Long, cancellationToken: cancellationToken).ConfigureAwait(false); - // Read the raw bytes (not a decoded string) so binary records survive the round-trip intact. + // Raw bytes keep binary records intact. var body = await response.Content.ReadAsByteArrayAsync(cancellationToken).ConfigureAwait(false); var contentType = response.Content.Headers.ContentType?.ToString(); return new KeyValueStoreRecord(key, body, string.IsNullOrEmpty(contentType) ? null : contentType); @@ -190,23 +177,16 @@ public Task RecordExistsAsync(string key, CancellationToken cancellationTo } } - /// - /// Stores a record with raw bytes and the given content type, honoring the given write options - /// (TimeoutSecs, DoNotRetryTimeouts, ContentEncoding). - /// + /// Stores a record with raw bytes and a content type. /// The record key. /// - /// The raw record bytes. With set, these must already be - /// encoded that way (e.g. already gzipped) — the client sends them as-is and does not compress them - /// again. + /// The raw record bytes. With , they must already be encoded that way + /// and are sent as they are. /// /// - /// The record's MIME type. Worth setting precisely for media and archives: the client skips - /// compressing a body whose content type already carries its own compression (images, audio, video, - /// archives, office/ZIP formats, web fonts). application/octet-stream — the usual choice for - /// data of an unknown or mixed binary type — is treated as compressible, since it could hold anything. - /// This content-type-based skip does not apply when is - /// set: that always skips the client's own compression, regardless of content type. + /// The record's MIME type. A body whose content type carries its own compression (images, audio, video, archives, + /// fonts) is not compressed by the client. application/octet-stream counts as compressible. A + /// always skips compression. /// /// Optional write options. /// A token to cancel the request. @@ -250,9 +230,8 @@ public Task DeleteRecordAsync(string key, CancellationToken cancellationToken = => _ctx.DeleteResourceAsync("records/" + ResourceContext.EncodePathSegment(key, nameof(key)), RequestTier.Short, cancellationToken); /// - /// Builds a public URL for fetching the given record. It fetches the store, and if the store exposes a - /// URL-signing secret key (i.e. it is private), appends an HMAC-SHA256 signature so the URL grants - /// access without an API token. The URL is built from the configured public base URL. + /// Builds a public URL for the record. For a private store (one with a URL-signing secret key), the URL carries an + /// HMAC-SHA256 signature, so it works without an API token. /// /// The record key. /// A token to cancel the request. @@ -273,12 +252,11 @@ public async Task GetRecordPublicUrlAsync(string key, CancellationToken } /// - /// Builds a public URL for listing this store's keys, forwarding the given key-listing filters into the - /// URL. As with , a signature is appended for private stores unless - /// the caller already supplied one. optionally bounds a signed URL. + /// Builds a public URL for listing the store's keys, forwarding the filters into the URL. As with + /// , a private store's URL is signed. /// /// Optional key-listing filters forwarded into the URL. - /// Optional expiry in seconds for a signed URL; must be positive (omit it for a URL that never expires). + /// Expiry of a signed URL in seconds; must be positive. Omit it for a URL that never expires. /// A token to cancel the request. public async Task CreateKeysPublicUrlAsync(ListKeysOptions? options = null, int? expiresInSecs = null, CancellationToken cancellationToken = default) { @@ -303,11 +281,9 @@ public async Task CreateKeysPublicUrlAsync(ListKeysOptions? options = nu } /// - /// Returns this client with every subsequent call's timeout set to , - /// overriding the tier default (see the "Timeout tiers" section of docs/README.md). Pass - /// for no timeout, matching the reference client's 'noTimeout'. A - /// value above (the overall budget) is capped at it — - /// raise itself to allow a longer per-call timeout. + /// Returns this client with the timeout of every subsequent call set to , overriding the + /// tier default (see "Timeout tiers" in docs/README.md). means no timeout. A value above + /// is capped at it. /// /// The timeout to use for every call made through this client. public KeyValueStoreClient WithTimeout(TimeSpan timeout) diff --git a/src/Apify.Client/Resources/KeyValueStoreCollectionClient.cs b/src/Apify.Client/Resources/KeyValueStoreCollectionClient.cs index 0ba2252..6e9a9fa 100644 --- a/src/Apify.Client/Resources/KeyValueStoreCollectionClient.cs +++ b/src/Apify.Client/Resources/KeyValueStoreCollectionClient.cs @@ -30,8 +30,8 @@ public Task> ListAsync(StorageListOptions? options } /// - /// Gets the store with the given name, creating it if it does not exist. An empty/null name - /// creates a new unnamed store. An optional is sent when creating the store. + /// Gets the store with the given name, creating it if it does not exist. An empty or null name creates an + /// unnamed store. An optional is sent on creation. /// /// The store name, or null for a new unnamed store. /// An optional store schema to send on creation. @@ -55,11 +55,9 @@ public IAsyncEnumerable IterateAsync(StorageListOptions? options /// - /// Returns this client with every subsequent call's timeout set to , - /// overriding the tier default (see the "Timeout tiers" section of docs/README.md). Pass - /// for no timeout, matching the reference client's 'noTimeout'. A - /// value above (the overall budget) is capped at it — - /// raise itself to allow a longer per-call timeout. + /// Returns this client with the timeout of every subsequent call set to , overriding the + /// tier default (see "Timeout tiers" in docs/README.md). means no timeout. A value above + /// is capped at it. /// /// The timeout to use for every call made through this client. public KeyValueStoreCollectionClient WithTimeout(TimeSpan timeout) diff --git a/src/Apify.Client/Resources/LogClient.cs b/src/Apify.Client/Resources/LogClient.cs index 23b6032..cb90411 100644 --- a/src/Apify.Client/Resources/LogClient.cs +++ b/src/Apify.Client/Resources/LogClient.cs @@ -9,10 +9,7 @@ namespace Apify.Client.Resources; -/// -/// A client for accessing the log of an Actor build or run (/v2/logs/{buildOrRunId}, or the -/// run/build-nested .../log). -/// +/// A client for the log of an Actor build or run. public sealed class LogClient { private readonly HttpClientCore _http; @@ -30,19 +27,16 @@ internal static LogClient ForId(HttpClientCore http, string baseUrl, string id) => new(http, ResourceContext.Single(http, baseUrl, "logs", id, "buildOrRunId"), ambiguousNotFound: false); /// - /// Creates a log client nested under a run or build that has no id of its own (run.Log(), - /// build.Log()). A 404 here is ambiguous — it could mean the run/build itself or its log is - /// missing — so and throw instead of resolving - /// null, matching the reference client's catchNotFoundForResourceOrThrow(). + /// Creates a log client nested under a run or build (run.Log(), build.Log()). A 404 is ambiguous there, + /// since the run or build may be missing, so and throw instead of + /// returning null, as in the reference client. /// internal static LogClient Nested(HttpClientCore http, string baseUrl, QueryParams? inheritedParams = null, TimeSpan? timeout = null) => new(http, ResourceContext.Collection(http, baseUrl, "log", inheritedParams).WithTimeout(timeout), ambiguousNotFound: true); /// - /// Fetches the log as text. For a log addressed by its own id (), a 404 - /// resolves to null; for a log nested under a run/build with no id of its own - /// (run.Log()/build.Log()), a 404 is ambiguous (the run/build itself may be what is - /// missing) and throws an instead. + /// Fetches the log as text. A log addressed by its own ID () resolves a 404 to + /// null; a nested log throws an . /// /// Optional log-content options. /// A token to cancel the request. @@ -59,10 +53,8 @@ internal static LogClient Nested(HttpClientCore http, string baseUrl, QueryParam => await _ctx.GetRawRequiredAsync("", q, RequestTier.Long, cancellationToken).ConfigureAwait(false); /// - /// Fetches the log as raw bytes instead of a decoded string — the counterpart of - /// for a log that is not (or might not be) valid UTF-8 text. It does not change how the log is fetched, - /// only how the body is returned; the whole log is downloaded in one response either way. To read it - /// incrementally, use . + /// Fetches the log as raw bytes, for a log that may not be valid UTF-8. The whole log is downloaded in one response; + /// use to read it incrementally. /// /// Optional log-content options. /// A token to cancel the request. @@ -79,14 +71,12 @@ internal static LogClient Nested(HttpClientCore http, string baseUrl, QueryParam => await _ctx.GetRawBytesRequiredAsync("", q, RequestTier.Long, cancellationToken).ConfigureAwait(false); /// - /// Opens a live, streaming connection to the log and returns a stream over the log bytes. For a log - /// addressed by its own id, a 404 resolves to null (matching ); for a log - /// nested under a run/build with no id of its own, a 404 is ambiguous and throws instead. + /// Opens a live stream of the log bytes. A log addressed by its own ID resolves a 404 to null, like + /// ; a nested log throws. /// /// - /// Unlike , this bypasses the buffered/retrying transport so the log can be - /// followed in real time as the run produces it (the stream=1 query parameter). Because the - /// response is consumed incrementally, it is not retried. The caller must dispose the returned stream. + /// Unlike , this bypasses the buffered, retrying transport (stream=1) to follow the log + /// in real time, so it is not retried. Dispose the returned stream. /// /// Optional log-content options. /// A token to cancel the request. @@ -116,11 +106,9 @@ internal static LogClient Nested(HttpClientCore http, string baseUrl, QueryParam } /// - /// Returns this client with every subsequent call's timeout set to , - /// overriding the tier default (see the "Timeout tiers" section of docs/README.md). Pass - /// for no timeout, matching the reference client's 'noTimeout'. A - /// value above (the overall budget) is capped at it — - /// raise itself to allow a longer per-call timeout. + /// Returns this client with the timeout of every subsequent call set to , overriding the + /// tier default (see "Timeout tiers" in docs/README.md). means no timeout. A value above + /// is capped at it. /// /// The timeout to use for every call made through this client. public LogClient WithTimeout(TimeSpan timeout) diff --git a/src/Apify.Client/Resources/NestedWebhookCollectionClient.cs b/src/Apify.Client/Resources/NestedWebhookCollectionClient.cs index 067cb9b..6102eb5 100644 --- a/src/Apify.Client/Resources/NestedWebhookCollectionClient.cs +++ b/src/Apify.Client/Resources/NestedWebhookCollectionClient.cs @@ -4,10 +4,8 @@ namespace Apify.Client.Resources; /// -/// A read-only client for the webhooks nested under an Actor (GET /v2/actors/{id}/webhooks) or a -/// task (GET /v2/actor-tasks/{id}/webhooks). These endpoints only support listing; webhooks are -/// created through the account-wide (which targets an Actor or task -/// via the webhook's condition), so Create is intentionally not exposed. +/// A read-only client for the webhooks nested under an Actor or task. These endpoints only list; create webhooks with +/// the account-wide , targeting the Actor or task in the webhook's condition. /// public sealed class NestedWebhookCollectionClient : AbstractWebhookCollectionClient { @@ -17,11 +15,9 @@ internal NestedWebhookCollectionClient(HttpClientCore http, string baseUrl) } /// - /// Returns this client with every subsequent call's timeout set to , - /// overriding the tier default (see the "Timeout tiers" section of docs/README.md). Pass - /// for no timeout, matching the reference client's 'noTimeout'. A - /// value above (the overall budget) is capped at it — - /// raise itself to allow a longer per-call timeout. + /// Returns this client with the timeout of every subsequent call set to , overriding the + /// tier default (see "Timeout tiers" in docs/README.md). means no timeout. A value above + /// is capped at it. /// /// The timeout to use for every call made through this client. public NestedWebhookCollectionClient WithTimeout(TimeSpan timeout) diff --git a/src/Apify.Client/Resources/RequestQueueClient.cs b/src/Apify.Client/Resources/RequestQueueClient.cs index b660a05..3dca0ca 100644 --- a/src/Apify.Client/Resources/RequestQueueClient.cs +++ b/src/Apify.Client/Resources/RequestQueueClient.cs @@ -13,22 +13,22 @@ namespace Apify.Client.Resources; -/// A client for a specific request queue (and run-nested variants). +/// A client for a specific request queue. public sealed class RequestQueueClient { - /// The API limit on requests per batch call; larger inputs are split into chunks of this size. + /// The API limit on requests per batch call. private const int MaxRequestsPerBatch = 25; /// - /// The API's maximum accepted request payload size (9 MiB). Batches are additionally split so no single - /// batch call exceeds this, matching the reference client's splitIntoJsonArrayBatches. + /// The API's maximum request payload size (9 MiB). Batches are also split to stay below it, as in the reference + /// client's splitIntoJsonArrayBatches. /// private const int MaxPayloadSizeBytes = 9 * 1024 * 1024; - /// Safety margin (0.01%) subtracted from the payload limit, matching the reference client. + /// Safety margin (0.01%) subtracted from the payload limit, as in the reference client. private const double PayloadSafetyBufferPercent = 0.0001; - /// The two bytes of the brackets that wrap a JSON array body, even one holding a single request. + /// The brackets that wrap a JSON array body. private const int JsonArrayBracketBytes = 2; private readonly HttpClientCore _http; @@ -56,9 +56,8 @@ internal static RequestQueueClient Nested(HttpClientCore http, string baseUrl, s => new(http, ResourceContext.NestedSingleton(http, baseUrl, subPath, inheritedParams).WithTimeout(timeout), null, timeout); /// - /// Returns a copy of the client that identifies its requests with . A - /// stable client key is required to operate on locks the client itself created, and lets the API detect - /// whether multiple clients access a queue. + /// Returns a copy of the client that identifies its requests with . A stable key is + /// needed to operate on locks the client itself created. /// /// The stable client key. public RequestQueueClient WithClientKey(string clientKey) => new(_http, _ctx, clientKey, _timeout); @@ -71,7 +70,7 @@ internal static RequestQueueClient Nested(HttpClientCore http, string baseUrl, s return data is JsonObject obj ? new RequestQueue(obj) : null; } - /// Updates the queue metadata (e.g. name) and returns the updated object. + /// Updates the queue metadata, such as its name, and returns the updated queue. /// Any JSON-serializable set of fields to update. /// A token to cancel the request. public async Task UpdateAsync(object newFields, CancellationToken cancellationToken = default) @@ -84,8 +83,7 @@ public async Task UpdateAsync(object newFields, CancellationToken public Task DeleteAsync(CancellationToken cancellationToken = default) => _ctx.DeleteResourceAsync("", RequestTier.Short, cancellationToken); /// - /// Returns the requests at the head (front) of the queue, up to (null - /// for the server default). + /// Returns up to requests from the head of the queue (null for the server default). /// /// The maximum number of requests to return. /// A token to cancel the request. @@ -97,7 +95,7 @@ public async Task ListHeadAsync(int? limit = null, Cancellatio return RequestQueueHead.FromData(await _ctx.GetResourceRequiredAsync("head", q, RequestTier.Short, cancellationToken).ConfigureAwait(false)); } - /// Adds a request to the queue. If is true, it is added to the front. + /// Adds a request to the queue, to the front if is true. /// The request to add. /// Whether to add to the front of the queue. /// A token to cancel the request. @@ -120,8 +118,8 @@ public async Task AddRequestAsync(RequestQueueRequest } /// - /// Updates an existing request (identified by its ID field) and returns the operation info. If - /// is true, the request is moved to the front of the queue. + /// Updates an existing request (identified by its ID) and moves it to the front of the queue if + /// is true. /// /// The request to update (must have an ID). /// Whether to move the request to the front. @@ -155,12 +153,12 @@ public async Task DeleteRequestAsync(string id, CancellationToken cancellationTo } catch (ApifyApiException e) when (HttpClientCore.IsNotFound(e)) { - // A missing request is a successful no-op for delete. + // A missing resource is a no-op. } } /// - /// Atomically returns and locks up to requests from the head of the queue for + /// Returns up to requests from the head of the queue and locks them for /// seconds. /// /// How long to lock the returned requests, in seconds. @@ -175,20 +173,14 @@ public async Task ListAndLockHeadAsync(int lockSecs, int return LockedRequestQueueHead.FromData(data); } - /// - /// Adds multiple requests to the queue. If is true, they are added to the - /// front. - /// + /// Adds multiple requests to the queue, to the front if is true. /// - /// The input is automatically split into chunks of at most 25 requests (the API count limit) that - /// additionally respect the API's ~9 MiB payload-size limit. Chunks are dispatched using up to - /// concurrent API calls (set it to 1 for sequential - /// dispatch). Requests the API returns as unprocessed in a successful response (typically rate-limited) - /// are retried with exponential backoff; the per-chunk results are merged in input order. Every request - /// must carry a non-empty UniqueKey. Consistent with the reference client, this method does not - /// throw on API errors: if a batch call fails and the transport did not retry, that chunk's - /// not-yet-processed requests are returned in . Invalid - /// input (empty uniqueKey, oversized request) is rejected up front with . + /// The input is split into chunks of at most 25 requests that also stay below the API's payload limit. Up to + /// chunks are sent concurrently (1 for sequential). Requests the API + /// returns as unprocessed are retried with exponential backoff, and the results are merged in input order. Every + /// request needs a non-empty UniqueKey; invalid input throws before anything + /// is sent. As in the reference client, an API error does not throw: the requests of the failed chunk are returned in + /// . /// /// The requests to add. /// Whether to add to the front of the queue. @@ -214,9 +206,8 @@ public async Task BatchAddRequestsAsync( var payloadSizeLimitBytes = MaxPayloadSizeBytes - (int)Math.Ceiling(MaxPayloadSizeBytes * PayloadSafetyBufferPercent); - // Serialize every request once: the byte lengths decide the batch boundaries, and the same strings are - // joined into the batch bodies, so nothing is serialized a second time when it is sent. All chunks are - // planned up front, so an oversized request is rejected before any batch is sent. + // Requests are serialized once: the byte lengths decide the batch boundaries and the same strings form the batch + // bodies. All chunks are planned first, so an oversized request is rejected before any batch is sent. var serialized = new List(list.Count); for (var i = 0; i < list.Count; i++) { @@ -239,7 +230,7 @@ public async Task BatchAddRequestsAsync( return merged; } - // Sequential path when parallelism is disabled or there is only a single chunk. + // Sequential when parallelism is off or there is a single chunk. if (options.MaxParallel <= 1 || chunks.Count == 1) { foreach (var chunk in chunks) @@ -250,9 +241,7 @@ public async Task BatchAddRequestsAsync( return merged; } - // Bounded-parallel dispatch: at most MaxParallel chunk calls run concurrently, gated by a semaphore. - // Results are merged in chunk (input) order so the output stays deterministic regardless of which - // chunk finishes first. + // At most MaxParallel chunks run at once. Results are merged in input order, whichever chunk finishes first. using var gate = new SemaphoreSlim(options.MaxParallel); var tasks = new List>(chunks.Count); foreach (var chunk in chunks) @@ -268,7 +257,7 @@ public async Task BatchAddRequestsAsync( return merged; } - /// Runs one chunk's add-with-retries under the concurrency gate. + /// Adds one chunk with retries, under the concurrency gate. private async Task DispatchChunkAsync( List chunk, bool forefront, @@ -287,20 +276,19 @@ private async Task DispatchChunkAsync( } } - /// A request together with its JSON serialization and that serialization's UTF-8 size. + /// A request with its JSON and the UTF-8 size of that JSON. private readonly record struct SerializedRequest(RequestQueueRequest Request, string Json, int ByteLength); /// - /// Splits serialized requests into batches of at most requests whose JSON - /// array bodies are at most bytes, always keeping at least one request per batch - /// so the split makes progress. Ports the reference client's splitIntoJsonArrayBatches. + /// Splits serialized requests into batches of at most requests and + /// bytes of JSON array, with at least one request per batch. Ports the reference + /// client's splitIntoJsonArrayBatches. /// private static List> SplitIntoJsonArrayBatches(List requests, int maxByteLength) { var batches = new List>(); var batch = new List(); - // One byte for the opening bracket; each request then adds its own bytes plus one for the comma or the - // closing bracket that follows it. + // One byte for the opening bracket; each request adds its bytes plus the comma or closing bracket after it. var byteLength = 1; foreach (var request in requests) { @@ -341,21 +329,18 @@ private async Task BatchAddChunkWithRetriesAsync(List serialized.Request); break; } processed.AddRange(response.ProcessedRequests); - // Only requests the API reports as unprocessed in this SUCCESSFUL response are retried. + // Only requests reported as unprocessed in a successful response are retried. unprocessed = new List(response.UnprocessedRequests); if (unprocessed.Count != 0) { - // As in the reference client, a response with unprocessed requests counts as rate limited: the - // SDK reads these counts to scale down. + // As in the reference client, a response with unprocessed requests counts as rate limited. _http.Stats.AddRateLimitError(attempt + 1); } @@ -405,7 +390,7 @@ private async Task BatchAddChunkAsync(List re return new BatchAddResult(processed, unprocessed); } - /// Joins already-serialized requests into a JSON array body without serializing them again. + /// Joins serialized requests into a JSON array body. private static string JoinAsJsonArray(List requests) => "[" + string.Join(",", requests.ConvertAll(static serialized => serialized.Json)) + "]"; @@ -439,16 +424,15 @@ private static Task SleepBackoffAsync(int attempt, int minDelayMillis, Cancellat return Task.CompletedTask; } - // (1 + random) * 2^attempt * minDelay — exponential backoff with jitter, matching the reference. + // Exponential backoff with jitter, as in the reference client: (1 + random) * 2^attempt * minDelay. var factor = (1 + Random.Shared.NextDouble()) * Math.Pow(2, attempt); var delayMillis = (int)Math.Floor(factor * minDelayMillis); return Task.Delay(delayMillis, cancellationToken); } /// - /// Deletes multiple requests in a single call. Each entry must have its - /// and/or set to identify the request to delete; other fields - /// are ignored. + /// Deletes multiple requests in one call. Each entry needs its or + /// ; other fields are ignored. /// /// The requests to delete, identified by Id and/or UniqueKey. /// A token to cancel the request. @@ -460,7 +444,7 @@ public async Task BatchDeleteRequestsAsync(IReadOnlyListEncodes a list of requests as their raw JSON objects, for a batch request body. + /// Encodes requests as their raw JSON objects for a batch body. private static List ToPayload(IReadOnlyCollection requests) { var payload = new List(requests.Count); @@ -487,8 +471,8 @@ public async Task ListRequestsAsync(ListRequestsOption } /// - /// Extends the lock on a request by seconds. If - /// is true, the request is moved to the front when its lock expires. + /// Extends the lock on a request by seconds. With , the + /// request moves to the front when the lock expires. /// /// The request ID. /// How much longer to hold the lock, in seconds. @@ -504,10 +488,7 @@ public async Task ProlongRequestLockAsync(string id, int lockSe return RequestLockInfo.FromData(await response.ReadDataObjectAsync(cancellationToken).ConfigureAwait(false)); } - /// - /// Releases the lock on a request. If is true, the request is moved to the - /// front of the queue. - /// + /// Releases the lock on a request, moving it to the front of the queue if is true. /// The request ID. /// Whether to move the request to the front. /// A token to cancel the request. @@ -523,7 +504,7 @@ public async Task DeleteRequestLockAsync(string id, bool forefront = false, Canc } catch (ApifyApiException e) when (HttpClientCore.IsNotFound(e)) { - // A missing lock is a successful no-op. + // A missing resource is a no-op. } } @@ -537,15 +518,12 @@ public async Task UnlockRequestsAsync(CancellationToken ca return UnlockRequestsResult.FromData(data); } - /// - /// Lazily iterates over the queue's requests, transparently following pagination. - /// + /// Lazily iterates the queue's requests, following pagination. /// - /// With no options it fetches pages of up to - /// requests until the queue is exhausted. The options mirror the reference client: Limit caps the - /// total number of requests yielded across all pages, ChunkSize (or its older name MaxPageLimit) caps the page size, - /// ExclusiveStartId/Cursor choose the starting point (first page only), and Filter - /// restricts to locked/pending requests. + /// Without options it fetches pages of up to requests until + /// the queue is exhausted. Limit caps the total, ChunkSize (or its older name MaxPageLimit) the + /// page size. ExclusiveStartId and Cursor choose the starting point, and Filter restricts the + /// requests to locked or pending ones. /// /// Optional iteration options. /// A token to cancel the iteration. @@ -594,7 +572,7 @@ public async IAsyncEnumerable PaginateRequestsAsync( yield break; } - // After the first page, paginate purely by cursor. + // After the first page, paginate by cursor only. nextExclusiveStartId = null; } } diff --git a/src/Apify.Client/Resources/RequestQueueCollectionClient.cs b/src/Apify.Client/Resources/RequestQueueCollectionClient.cs index f0cd1ae..7356074 100644 --- a/src/Apify.Client/Resources/RequestQueueCollectionClient.cs +++ b/src/Apify.Client/Resources/RequestQueueCollectionClient.cs @@ -28,10 +28,7 @@ public Task> ListAsync(StorageListOptions? options return _ctx.ListResourceAsync("", q, static d => new RequestQueue(d), RequestTier.Medium, cancellationToken); } - /// - /// Gets the queue with the given name, creating it if it does not exist. An empty/null name - /// creates a new unnamed queue. - /// + /// Gets the queue with the given name, creating it if it does not exist. An empty or null name creates an unnamed queue. /// The queue name, or null for a new unnamed queue. /// A token to cancel the request. public async Task GetOrCreateAsync(string? name = null, CancellationToken cancellationToken = default) @@ -53,11 +50,9 @@ public IAsyncEnumerable IterateAsync(StorageListOptions? options = /// - /// Returns this client with every subsequent call's timeout set to , - /// overriding the tier default (see the "Timeout tiers" section of docs/README.md). Pass - /// for no timeout, matching the reference client's 'noTimeout'. A - /// value above (the overall budget) is capped at it — - /// raise itself to allow a longer per-call timeout. + /// Returns this client with the timeout of every subsequent call set to , overriding the + /// tier default (see "Timeout tiers" in docs/README.md). means no timeout. A value above + /// is capped at it. /// /// The timeout to use for every call made through this client. public RequestQueueCollectionClient WithTimeout(TimeSpan timeout) diff --git a/src/Apify.Client/Resources/RunClient.cs b/src/Apify.Client/Resources/RunClient.cs index 533e268..b0ba049 100644 --- a/src/Apify.Client/Resources/RunClient.cs +++ b/src/Apify.Client/Resources/RunClient.cs @@ -15,12 +15,9 @@ namespace Apify.Client.Resources; /// -/// A client for a specific Actor run. +/// A client for a specific Actor run: get, update, delete, abort, metamorph, reboot, resurrect, charge, and wait for +/// it to finish, plus accessors for its default storages and log. /// -/// -/// It provides CRUD methods plus convenience helpers (abort, metamorph, reboot, resurrect, charge, -/// wait-for-finish) and accessors for the run's default storages and log. -/// public sealed class RunClient { /// Header the API uses to deduplicate charge requests. @@ -40,8 +37,8 @@ internal RunClient(ApifyClient root, HttpClientCore http, string baseUrl, string } /// - /// Pins the status/origin query parameters inherited by all calls on this client (used by - /// the last-run accessors). Empty values are skipped. + /// Pins the status and origin query parameters of every call on this client, for the last-run + /// accessors. Empty values are skipped. /// internal void SetLastRunParams(LastRunOptions options) { @@ -57,8 +54,8 @@ internal void SetLastRunParams(LastRunOptions options) } /// - /// Fetches the run, optionally asking the API to wait up to - /// seconds (max 60) for the run to reach a terminal state. Returns null if it does not exist. + /// Fetches the run, optionally asking the API to wait up to seconds (at most + /// 60) for it to finish. Returns null if the run does not exist. /// /// Optional server-side wait in seconds. /// A token to cancel the request. @@ -66,8 +63,7 @@ internal void SetLastRunParams(LastRunOptions options) { var q = new QueryParams(); q.AddInt("waitForFinish", _ctx.ClampServerWait(waitForFinishSecs)); - // Long: the clamped waitForFinish can hold the connection open close to the overall budget, so the - // HTTP timeout for this call must cover it (matches the reference's short-base-extended-for-wait). + // Long tier: the server may hold the connection for most of the wait. var data = await _ctx.GetResourceAsync("", q, RequestTier.Long, cancellationToken).ConfigureAwait(false); return data is JsonObject obj ? new ActorRun(obj) : null; } @@ -85,9 +81,8 @@ public async Task UpdateAsync(object newFields, CancellationToken canc public Task DeleteAsync(CancellationToken cancellationToken = default) => _ctx.DeleteResourceAsync("", RequestTier.Short, cancellationToken); /// - /// Aborts the run. If is true, the run is signalled so it can - /// finish the current request before terminating; false aborts immediately. null omits - /// the parameter and lets the server apply its default (immediate abort). + /// Aborts the run. true lets it finish the current request first, false aborts at once, and null + /// leaves the choice to the server (an immediate abort). /// /// Whether to abort gracefully, or null for the server default. /// A token to cancel the request. @@ -100,10 +95,7 @@ public async Task AbortAsync(bool? gracefully = null, CancellationToke /// Transforms the run into a run of another Actor with a new input. /// The Actor to metamorph into. - /// - /// The new input: a plain object or array serialized to JSON, raw bytes (byte[]) sent as-is, or - /// null for none. - /// + /// The new input: byte[] is sent as it is, anything else as JSON, null for none. /// Optional metamorph options. /// A token to cancel the request. public async Task MetamorphAsync( @@ -124,7 +116,7 @@ public async Task MetamorphAsync( return new ActorRun(await _ctx.PostWithBodyAsync("metamorph", q, body, options.ContentTypeOrDefault(), RequestTier.Medium, cancellationToken, bodyBytes).ConfigureAwait(false)); } - /// Reboots the run (restarts its container while keeping the same run). + /// Restarts the run's container, keeping the same run. /// A token to cancel the request. public async Task RebootAsync(CancellationToken cancellationToken = default) { @@ -132,8 +124,8 @@ public async Task RebootAsync(CancellationToken cancellationToken = de } /// - /// Resurrects a finished run (status SUCCEEDED, FAILED, ABORTED or TIMED-OUT) and - /// restarts its container with the same storages. + /// Restarts a finished run (SUCCEEDED, FAILED, ABORTED, or TIMED-OUT) in its container, + /// with the same storages. /// /// Optional resurrect options. /// A token to cancel the request. @@ -144,13 +136,10 @@ public async Task ResurrectAsync(RunResurrectOptions? options = null, return new ActorRun(await _ctx.PostWithBodyAsync("resurrect", q, null, "", RequestTier.Medium, cancellationToken).ConfigureAwait(false)); } - /// - /// Charges for a pay-per-event Actor run: records occurrences of a named event. Only meaningful for - /// runs of pay-per-event Actors. - /// + /// Charges for events of a pay-per-event Actor run. /// - /// An idempotency key is always sent (auto-generated if not provided), so a charge that is retried by - /// the transport is applied at most once, matching the reference client. + /// The request always carries an idempotency key (generated when not given), so a charge the transport retries is + /// applied once. /// /// The charge event details. /// A token to cancel the request. @@ -188,10 +177,7 @@ public async Task ChargeAsync(RunChargeOptions options, CancellationToken cancel cancellationToken: cancellationToken).ConfigureAwait(false); } - /// - /// Builds a per-charge idempotency key of the form "{runId}-{eventName}-{millis}-{random}". It - /// need not be cryptographically secure, only unique enough to avoid collisions within a request. - /// + /// Builds an idempotency key {runId}-{eventName}-{millis}-{random}; it only has to be unique, not secure. private string GenerateIdempotencyKey(string eventName) { return string.Format( @@ -204,8 +190,8 @@ private string GenerateIdempotencyKey(string eventName) } /// - /// Polls until the run reaches a terminal state or elapses (null - /// waits indefinitely). Returns the latest run. + /// Polls until the run reaches a terminal state or elapses (null waits + /// indefinitely) and returns the latest run. /// /// The wait budget in seconds, or null to wait indefinitely. /// A token to cancel the wait. @@ -216,8 +202,7 @@ public async Task WaitForFinishAsync(int? waitSecs = null, Cancellatio } /// - /// Builds the reference client's default sink: the run and its Actor are fetched for the - /// <Actor name> runId:<id> -> prefix. + /// Builds the default log sink of the reference client: lines prefixed with <Actor name> runId:<id> -> . /// private async Task> CreateDefaultSinkAsync(CancellationToken cancellationToken) { @@ -227,10 +212,7 @@ private async Task> CreateDefaultSinkAsync(CancellationToken canc return StreamedLog.CreatePrefixedSink(run?.Id ?? _id, actor?.Name); } - /// - /// Waits for the run to finish, optionally redirecting its live log to for the - /// duration of the wait. Shared by Actor.Call/Task.Call's log option. - /// + /// Waits for the run to finish, redirecting its live log to meanwhile. internal async Task WaitForFinishWithLogAsync(int? waitSecs, Action? log, CancellationToken cancellationToken) { if (log is null) @@ -256,18 +238,14 @@ internal async Task WaitForFinishWithLogAsync(int? waitSecs, Action - /// Lazily iterates over the items of the run's default dataset while the run is still producing them. - /// Mirrors the reference client's iterateDatasetItems(). + /// Lazily iterates the items of the run's default dataset while the run is still producing them, as the reference + /// client's iterateDatasetItems() does. /// /// - /// While the run has not finished, each poll yields the rows below the dataset's itemCount and - /// then waits up to for the run to finish, - /// so the last rows are read as soon as it does. Each page is requested with a limit that ends at - /// itemCount, so it covers exactly the rows it asks for, whatever the filters or - /// do to the items. itemCount lags a few - /// seconds behind the pushed items, so once the run reaches a terminal status the rows past it are read - /// a page at a time until none are left. On a last-run client, the iteration sticks to the run its - /// first request resolves to, so a newer run cannot swap the dataset mid-iteration. + /// While the run is active, each poll yields the rows below the dataset's itemCount, then waits up to + /// for the run to finish. itemCount lags a few + /// seconds behind the pushed items, so after the run finishes the remaining rows are read page by page. A last-run + /// client sticks to the run its first request resolved to. /// /// Optional pagination, filtering and polling options. /// A token to cancel the iteration. @@ -284,12 +262,11 @@ internal async Task WaitForFinishWithLogAsync(int? waitSecs, Action 0 ? (long)position + options.Limit.Value : null; var run = await GetAsync(cancellationToken: cancellationToken).ConfigureAwait(false); - // A last-run client resolves `runs/last` on each request, so a newer run would swap the dataset - // mid-iteration: pin to the run the first request resolved to. + // A last-run client resolves `runs/last` on each request; pin the run of the first one so a newer run cannot swap the dataset. var runClient = this; if (run?.Id is { } resolvedId && resolvedId != _id) { @@ -347,9 +324,8 @@ internal async Task WaitForFinishWithLogAsync(int? waitSecs, Action