diff --git a/.editorconfig b/.editorconfig index a36b2b0..a554cd7 100644 --- a/.editorconfig +++ b/.editorconfig @@ -22,3 +22,9 @@ indent_size = 2 [*.{yml,yaml,json,md}] indent_size = 2 + +[tests/**.cs] +# CA1861: tests state their input data inline, as literal arrays at the call site; hoisting each into a static +# field would separate the data from the assertion that depends on it, and test code has no hot path where the +# repeated allocation matters. +dotnet_diagnostic.CA1861.severity = none diff --git a/.github/workflows/dotnet-integration-tests.yml b/.github/workflows/dotnet-integration-tests.yml index db93a32..487f2f2 100644 --- a/.github/workflows/dotnet-integration-tests.yml +++ b/.github/workflows/dotnet-integration-tests.yml @@ -50,9 +50,10 @@ jobs: - name: Build (warnings as errors) run: dotnet build --configuration Release --no-restore - # Offline unit tests (mock transport): prove the retry/error/signature logic without the API. + # Offline unit tests (mock transport): prove the retry/error/signature logic without the API. Every test step + # sets TreatNoTestsAsError, so a filter that matches no tests fails the workflow instead of passing. - name: Unit tests - run: dotnet test --configuration Release --no-build --filter Category=Unit + run: dotnet test --configuration Release --no-build --filter Category=Unit -- RunConfiguration.TreatNoTestsAsError=true # Fail fast if the integration-test secret is missing or empty. Without this guard the # integration tests silently skip (they self-skip when APIFY_TOKEN is unset), so a green run @@ -70,11 +71,11 @@ jobs: env: # The integration-test token is stored as a repository secret. APIFY_TOKEN: ${{ secrets.APIFY_TOKEN }} - run: dotnet test --configuration Release --no-build --filter Category=Integration + run: dotnet test --configuration Release --no-build --filter Category=Integration -- RunConfiguration.TreatNoTestsAsError=true # Standalone CI step that verifies the documentation examples: the Examples suite runs each example # end-to-end against the live API and compiles every C# snippet in the README and docs/. - name: Test examples env: APIFY_TOKEN: ${{ secrets.APIFY_TOKEN }} - run: dotnet test --configuration Release --no-build --filter Category=Examples + run: dotnet test --configuration Release --no-build --filter Category=Examples -- RunConfiguration.TreatNoTestsAsError=true diff --git a/CHANGELOG.md b/CHANGELOG.md index c671cce..73e9893 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,47 @@ # Changelog +## 0.8.0 + +- `PushItemsAsync` no longer retries a request that timed out, because the API may already have stored the + items and a retry would store them twice. It also serializes the items once, without building a document + tree. +- `ApifyClientOptions.PublicBaseUrl` now defaults to `https://api.apify.com` instead of `BaseUrl`, as in + the reference client. +- Added `ApifyClient.Stats` (`Statistics`: `Calls`, `Requests`, `RateLimitErrors`). +- Added `ApifyClientOptions.TimeoutLongSecs`, the base of the `Long` timeout tier (default `TimeoutSecs`). + `TimeoutSecs` stays the overall cap, the reference client's `timeoutMaxSecs`. +- Added `StreamedLog.DefaultConsole`: pass it as `log` to `CallAsync` to redirect the run log to the console + with the reference client's ` runId: -> ` prefix, and `StreamedLog.CreatePrefixedSink` for + a run that is already known. Documented that omitting `log` does not redirect the run log, unlike the + reference client. +- `ApifyClient.UserAgent` reports a `User-Agent` entry of `ApifyClientOptions.Headers`. +- `GzipHttpCompressor` throws `ArgumentOutOfRangeException` for an undefined `CompressionLevel`. +- Invalid record keys, request IDs and resource IDs now report the caller's parameter name in the + `ArgumentException`. +- A batch add with unprocessed requests counts as a rate limit error in `ApifyClient.Stats`, as in the + reference client. +- Bumped `ApifyClientVersion.ApiSpecVersion` to the Apify OpenAPI spec `v2-2026-10-29T100357Z` and + the project version to `0.8.0`. +- Regenerated the embedded response schemas; `Schedule.uiState` accepts `null`, which the API returns. +- Added typed accessors: `Build.Changelog`/`ExitCode`/`GitBranchName`/`GitCommitId`, + `ActorRun.ConsoleUrl`/`Standby` (the Server Actor deployment), `Title` on `Dataset`/`KeyValueStore`/`RequestQueue`/`Webhook`/`Schedule`, + `Webhook.IsEnabled`/`IsApifyIntegration`, `Schedule.Description`/`UiState`, and + `RequestQueueRequest.LockByClient`/`HandledAt`. +- Added `RunListOptions.Deployment` (filters Standby runs) and `StoreListOptions.IncludeInputSchema`. +- `UserClient.MonthlyUsageAsync`, `LimitsAsync` and `UpdateLimitsAsync` now also work on `User(id)`. +- Resource ids, record keys and request IDs are percent-encoded; every `/` of a resource id becomes `~`, and + an empty value or a `.`/`..` segment throws `ArgumentException`. +- `UpdateRequestAsync` throws `ArgumentException` for a request without an `Id`. +- `BatchAddRequestsAsync` serializes each request once and rejects an oversized request before sending + any batch. +- `PushItemsAsync` throws `ArgumentException` for anything but an object or an array of objects, and sends + a `string` as serialized JSON. +- Added `ApifyClientOptions.Headers`, headers sent with every request. +- Added `ApifyClientOptions.Compressor`, `HttpCompressor`, `BrotliHttpCompressor` (quality 0 to 11) and + `GzipHttpCompressor` for a custom compression quality or algorithm. +- Documented that `GetRecordAsync` never parses a record body. +- The integration tests wait up to 90 seconds for a new resource to appear in a listing. + ## 0.7.1 - Bumped `ApifyClientVersion.ApiSpecVersion` to the Apify OpenAPI spec `v2-2026-10-26T112451Z` and diff --git a/README.md b/README.md index cc74149..c5216b8 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ dotnet add package Apify.Client ## Quick start -`ImplicitUsings` is disabled in this repository, so every `using` (even `System`) is listed explicitly. +The snippets list every `using` explicitly; a project with `ImplicitUsings` enabled can omit `using System;`. ```csharp using System; @@ -31,8 +31,8 @@ using Apify.Client; var client = new ApifyClient("my-api-token"); -// Start an Actor and wait for it to finish (null waits indefinitely; pass seconds to bound the wait). -var run = await client.Actor("apify/hello-world").CallAsync(null, null, null); +// 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(); diff --git a/docs/README.md b/docs/README.md index 26a22b9..0fc2fad 100644 --- a/docs/README.md +++ b/docs/README.md @@ -45,9 +45,9 @@ using Apify.Client; var client = new ApifyClient("my-api-token"); -// Start an Actor and wait for it to finish. The last argument is the wait budget in seconds; -// pass a value (e.g. 120) to bound the wait, or null to wait indefinitely (as here). -var run = await client.Actor("apify/hello-world").CallAsync(null, null, null); +// Start an Actor and wait for it to finish. Pass waitSecs (e.g. 120) to bound the wait; +// by default it waits indefinitely. +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(); @@ -112,27 +112,36 @@ var configured = new ApifyClient(new ApifyClientOptions |---|---|---| | `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` | `BaseUrl` | Base URL used when building public, shareable resource URLs. Also accepted with or without `/v2`. | +| `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`. | | `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` | Base duration of the `Long` timeout tier (downloads, uploads, streaming); also the overall per-request cap every attempt's growing timeout is clamped to, regardless of tier. | +| `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. | | `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. | | `HttpTransport` | `HttpClientTransport` | The replaceable transport (`Apify.Client.Http.IHttpTransport`). | Requests are retried on network errors, HTTP 429 (rate limit) and 5xx responses, with exponential backoff and jitter. 4xx responses (other than 429) are thrown immediately as `ApifyApiException`. +### Request 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). + ### 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`/`TimeoutSecs`). +(`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 @@ -146,9 +155,35 @@ itself for a longer per-call timeout; a shorter override still narrows the cap, TimeoutSecs = ... })`) and `SetRecordAsync` (via `SetRecordOptions.TimeoutSecs`) already had their own equivalent, narrower overrides before `WithTimeout` existed and keep them instead. -Request bodies of at least 1024 bytes are compressed before sending. Brotli is used by default; set +### 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. +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`. + +```csharp +using Apify.Client; +using Apify.Client.Http; + +var client = new ApifyClient(new ApifyClientOptions +{ + Token = "", + Compressor = new BrotliHttpCompressor(quality: 11), +}); +``` + +### 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 diff --git a/docs/actors.md b/docs/actors.md index c55ee7c..725a2e6 100644 --- a/docs/actors.md +++ b/docs/actors.md @@ -43,7 +43,9 @@ foreach (var actor in page.Items) - `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). Time spent retrying + 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`. - `ValidateInputAsync(object? input = null, ValidateInputOptions? options = null)` → `bool`. diff --git a/docs/misc.md b/docs/misc.md index 8871884..9bfff8c 100644 --- a/docs/misc.md +++ b/docs/misc.md @@ -25,6 +25,7 @@ Browse public Actors in the [Apify Store](https://apify.com/store). | `PricingModel` | `string?` | Filter by pricing model (`FREE`, `PRICE_PER_DATASET_ITEM`, `PAY_PER_EVENT`; the rental model `FLAT_PRICE_PER_MONTH` is no longer supported). | | `IncludeUnrunnableActors` | `bool?` | Include Actors that cannot currently be run. | | `AllowsAgenticUsers` | `bool?` | Only Actors that permit agentic (automated) users. | +| `IncludeInputSchema` | `bool?` | Include each Actor's compact `inputSchema` (read it with `Get("inputSchema")`). The API then caps `limit` at 10, so iteration requests pages of at most 10. | | `ResponseFormat` | `string?` | Requested response format. | ```csharp @@ -43,9 +44,14 @@ await foreach (var item in client.Store().IterateAsync(new StoreListOptions { Se - `GetAsync()` → `User?`. For `Me()` the raw payload includes private account details (`ToJsonObject()`); for `User(id)` it returns the public profile. -- `MonthlyUsageAsync(string? date = null)` → `JsonObject` (only for `Me()`; `date` is `YYYY-MM-DD`, and - `null` reports the current month). -- `LimitsAsync()` → `JsonObject` / `UpdateLimitsAsync(object newLimits)` (only for `Me()`). +- `MonthlyUsageAsync(string? date = null)` → `JsonObject` (`date` is `YYYY-MM-DD`, and `null` reports the + current month). +- `LimitsAsync()` → `JsonObject` / `UpdateLimitsAsync(object newLimits)`. + +The usage and limits methods work on `Me()` and on `User(id)` with the account's ID or username, but only for +your own account or the organization your API token belongs to. API tokens with limited permissions cannot +use the by-ID form (`Me()` is not affected), and updating an organization's limits also needs the permission to +manage it. ```csharp using System; @@ -56,7 +62,7 @@ var me = await client.Me().GetAsync(); Console.WriteLine(me?.Username); var usage = await client.Me().MonthlyUsageAsync(); -// Any user's public profile by id (no private fields, and no account-only methods). +// Any user's public profile by id (no private fields). var otherUser = await client.User("some-user-id").GetAsync(); Console.WriteLine(otherUser?.Username); ``` diff --git a/docs/models.md b/docs/models.md index 26c267b..cf88551 100644 --- a/docs/models.md +++ b/docs/models.md @@ -58,7 +58,9 @@ Base class for every JSON-backed model below. | `DefaultDatasetId` | `string?` | ID of the run's default dataset. | | `DefaultKeyValueStoreId` | `string?` | ID of the run's default key-value store. | | `DefaultRequestQueueId` | `string?` | ID of the run's default request queue. | -| `ContainerUrl` | `string?` | URL of the run's container (for live access while running). | +| `ContainerUrl` | `string?` | URL of the run's container (for live access while running). Normalized to its absolute-URI form. | +| `ConsoleUrl` | `string?` | URL of the run's page in Apify Console. Normalized to its absolute-URI form. | +| `Standby` | `JsonObject?` | Server Actor deployment of the run: its `deployment` names the serving pool (`SINGLE_TENANT`, `MULTI_TENANT_OWNER_PAID`, `MULTI_TENANT_FREE_USERS` or `MULTI_TENANT_PAYING_USERS`); `null` for a run not started by a Server Actor request. | | `IsTerminal` | `bool` | `true` if `Status` is a terminal state (succeeded/failed/aborted/timed-out). | ## `Build` @@ -72,6 +74,10 @@ Base class for every JSON-backed model below. | `FinishedAt` | `string?` | ISO 8601 finish timestamp (`null` while building). | | `BuildNumber` | `string?` | The semantic build number. | | `ImageDigest` | `string?` | Digest of the built Docker image manifest (no `sha256:` prefix); compare two builds' digests to tell whether their image contents differ. | +| `Changelog` | `string?` | The changelog the Actor's source provided for the build. | +| `ExitCode` | `long?` | Exit code of the build process (`0` on success); absent while building. | +| `GitBranchName` | `string?` | Git branch the build's source was taken from, for Git-based sources. | +| `GitCommitId` | `string?` | Git commit the build's source was taken from, for Git-based sources. | | `IsTerminal` | `bool` | `true` if `Status` is a terminal state. | ## `ActorVersion` @@ -141,6 +147,7 @@ An entry returned when browsing the Apify Store. | `CreatedAt` | `string?` | ISO 8601 creation timestamp. | | `ModifiedAt` | `string?` | ISO 8601 last-modification timestamp. | | `ItemCount` | `long?` | Number of items stored in the dataset. | +| `Title` | `string?` | The dataset's human-readable title, if set. | ## `KeyValueStore` @@ -151,6 +158,7 @@ An entry returned when browsing the Apify Store. | `UserId` | `string?` | ID of the owning user. | | `CreatedAt` | `string?` | ISO 8601 creation timestamp. | | `ModifiedAt` | `string?` | ISO 8601 last-modification timestamp. | +| `Title` | `string?` | The store's human-readable title, if set. | ## `KeyValueStoreRecord` @@ -192,6 +200,7 @@ One page of key listings (returned by `ListKeysAsync`). | `CreatedAt` | `string?` | ISO 8601 creation timestamp. | | `ModifiedAt` | `string?` | ISO 8601 last-modification timestamp. | | `TotalRequestCount` | `long?` | Total number of requests ever added to the queue. | +| `Title` | `string?` | The queue's human-readable title, if set. | ## `RequestQueueRequest` @@ -207,6 +216,8 @@ A read/write model (request input and response). Fields set to `null` are omitte | `UserData` | `JsonNode?` | Arbitrary user-defined JSON payload attached to the request. | | `RetryCount` | `long?` | Number of times the request has been retried (assigned by the queue). | | `LockExpiresAt` | `string?` | ISO 8601 lock expiry; only set on requests returned by `ListAndLockHeadAsync`. | +| `LockByClient` | `string?` | Client key of the client holding the request's lock; only set on locked requests. | +| `HandledAt` | `string?` | ISO 8601 time the request was handled; `null` while it is still pending. | ## `RequestQueueHead` @@ -300,6 +311,9 @@ The aggregate result of a batch add-requests operation. | `Name` | `string?` | The schedule's name. | | `CronExpression` | `string?` | The cron expression controlling when it fires. | | `IsEnabled` | `bool?` | Whether the schedule is currently enabled. | +| `Title` | `string?` | The schedule's human-readable title, if set. | +| `Description` | `string?` | The schedule's description, if set. | +| `UiState` | `JsonObject?` | State of the Console's schedule editor; `null` once an API update changed the cron expression or timezone and the state was dropped. | ## `Webhook` @@ -309,6 +323,9 @@ The aggregate result of a batch add-requests operation. | `UserId` | `string?` | ID of the owning user. | | `RequestUrl` | `string?` | URL the webhook posts to when triggered. | | `EventTypes` | `IReadOnlyList?` | The event types that trigger the webhook. | +| `Title` | `string?` | The webhook's human-readable title, if set. | +| `IsEnabled` | `bool?` | Whether the webhook is enabled; a disabled webhook is not dispatched. | +| `IsApifyIntegration` | `bool?` | Whether an Apify integration created the webhook rather than the user directly. | ## `WebhookDispatch` diff --git a/docs/runs.md b/docs/runs.md index 08295b3..41f6621 100644 --- a/docs/runs.md +++ b/docs/runs.md @@ -11,8 +11,11 @@ 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`), -`StartedAfter` and `StartedBefore` (ISO 8601 bounds, honoured only by the Actor- and task-scoped run -collections). +`Deployment` (`IReadOnlyList?`, filters Actor Standby runs by the pool serving 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 +collections, not by task runs). ## Single run — `client.Run(runId)` diff --git a/docs/storages.md b/docs/storages.md index b73b2cd..9394b2e 100644 --- a/docs/storages.md +++ b/docs/storages.md @@ -36,7 +36,9 @@ 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. +- `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). - `CreateItemsPublicUrlAsync(DatasetListItemsOptions? options = null, int? expiresInSecs = null, DownloadItemsFormat? format = null)` → `string` (a signed public URL; `format` defaults to `Json`). diff --git a/docs/tasks.md b/docs/tasks.md index ec2e01f..76ec1a5 100644 --- a/docs/tasks.md +++ b/docs/tasks.md @@ -23,7 +23,9 @@ a specific task with `client.Task(id)`. the task's Actor. Unpublishing a task that is not published does nothing. - `StartAsync(object? input = null, TaskStartOptions? options = null)` → `ActorRun`. - `CallAsync(object? input = null, TaskStartOptions? options = null, int? waitSecs = null, Action? log = null)` - → `ActorRun` (`log`, if set, redirects the run's live log to that sink for the duration of the wait). + → `ActorRun` (`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). - `GetInputAsync()` → `JsonNode?` (throws if the task does not exist; a task without stored input yields an empty JSON object) / `UpdateInputAsync(object input)` → `JsonNode?`. - `LastRun(LastRunOptions? options = null)` → `RunClient`; `Runs()` → `RunCollectionClient`. diff --git a/src/Apify.Client/Apify.Client.csproj b/src/Apify.Client/Apify.Client.csproj index 5b97159..8788d59 100644 --- a/src/Apify.Client/Apify.Client.csproj +++ b/src/Apify.Client/Apify.Client.csproj @@ -6,7 +6,7 @@ Apify.Client - 0.7.1 + 0.8.0 Apify Apify Apify API client for .NET diff --git a/src/Apify.Client/ApifyClient.cs b/src/Apify.Client/ApifyClient.cs index a3ff367..3f4bcde 100644 --- a/src/Apify.Client/ApifyClient.cs +++ b/src/Apify.Client/ApifyClient.cs @@ -61,6 +61,7 @@ public sealed class ApifyClient private const string MeUserPlaceholder = "me"; private readonly HttpClientCore _http; + private readonly Statistics _stats = new(); private readonly string _baseUrl; private readonly string _publicBaseUrl; @@ -84,14 +85,29 @@ public ApifyClient(ApifyClientOptions options) options.MinDelayBetweenRetriesMillis, maxDelayMillis, options.TimeoutSecs, + options.TimeoutLongSecs ?? options.TimeoutSecs, options.TimeoutShortSecs, options.TimeoutMediumSecs); var userAgent = BuildUserAgent(options.UserAgentSuffix, options.IsAtHome ?? DefaultIsAtHome); - _http = new HttpClientCore(transport, options.Token, userAgent, retry, options.RequestCompression); + _http = new HttpClientCore(transport, options.Token, userAgent, retry, ResolveCompressor(options), _stats, options.Headers); _baseUrl = ToApiBaseUrl(options.BaseUrl); - _publicBaseUrl = ToApiBaseUrl(options.PublicBaseUrl ?? options.BaseUrl); + _publicBaseUrl = ToApiBaseUrl(options.PublicBaseUrl ?? DefaultBaseUrl); + } + + /// + /// The compressor for large request bodies: the explicit , or the + /// built-in one for with its default quality. + /// + private static HttpCompressor ResolveCompressor(ApifyClientOptions options) + { + if (options.Compressor is not null) + { + return options.Compressor; + } + + return options.RequestCompression == RequestCompression.Gzip ? new GzipHttpCompressor() : new BrotliHttpCompressor(); } /// The /v2 API version path, appended to a base URL that does not already end with it. @@ -110,9 +126,18 @@ private static string ToApiBaseUrl(string url) return trimmed.EndsWith(ApiVersionPath, StringComparison.Ordinal) ? trimmed : trimmed + ApiVersionPath; } - /// The User-Agent header value this client sends. + /// + /// 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. + /// 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. + /// + public Statistics Stats => _stats; + /// The fully-qualified API base URL this client targets (including the /v2 suffix). public string ApiBaseUrl => _baseUrl; diff --git a/src/Apify.Client/ApifyClientOptions.cs b/src/Apify.Client/ApifyClientOptions.cs index fc43659..5fd87c0 100644 --- a/src/Apify.Client/ApifyClientOptions.cs +++ b/src/Apify.Client/ApifyClientOptions.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Apify.Client.Http; namespace Apify.Client; @@ -20,8 +21,10 @@ public sealed class ApifyClientOptions public string BaseUrl { get; set; } = ApifyClient.DefaultBaseUrl; /// - /// Base URL for building public, shareable resource URLs (defaults to ). Like - /// , accepted with or without the /v2 API version path. + /// 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. /// public string? PublicBaseUrl { get; set; } @@ -35,13 +38,20 @@ public sealed class ApifyClientOptions public int? MaxDelayBetweenRetriesMillis { get; set; } /// - /// Base duration of the Long timeout tier, in seconds (default 360): downloads, uploads and - /// streaming. Also the overall per-request cap every attempt's growing timeout is clamped to, - /// regardless of tier, matching the reference client's timeoutLongSecs/timeoutMaxSecs - /// (which share the same 360s default for the same reason). + /// 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. /// 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. + /// + 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. @@ -58,11 +68,29 @@ public sealed class ApifyClientOptions public string? UserAgentSuffix { get; set; } /// - /// Algorithm used to compress large request bodies (default ). - /// Set it to to send gzip-compressed bodies instead. + /// 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. /// 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. + /// + 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. + /// + public IReadOnlyDictionary? Headers { get; set; } + /// Replaces the default transport (). public IHttpTransport? HttpTransport { get; set; } diff --git a/src/Apify.Client/ApifyClientVersion.cs b/src/Apify.Client/ApifyClientVersion.cs index 7fd9a10..d949c58 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.7.1"; + public const string ClientVersion = "0.8.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-10-26T112451Z"; + public const string ApiSpecVersion = "v2-2026-10-29T100357Z"; } diff --git a/src/Apify.Client/Http/BrotliHttpCompressor.cs b/src/Apify.Client/Http/BrotliHttpCompressor.cs new file mode 100644 index 0000000..e64d663 --- /dev/null +++ b/src/Apify.Client/Http/BrotliHttpCompressor.cs @@ -0,0 +1,55 @@ +using System; +using System.IO.Compression; +using System.Threading; +using System.Threading.Tasks; + +namespace Apify.Client.Http; + +/// +/// Compresses request bodies with brotli (Content-Encoding: br). Matches the reference client's +/// BrotliHttpCompressor. +/// +public sealed class BrotliHttpCompressor : HttpCompressor +{ + /// The lowest valid brotli quality: the fastest, with the least compression. + public const int MinQuality = 0; + + /// The highest valid brotli quality: the slowest, with the best compression. + public const int MaxQuality = 11; + + /// The default quality, the middle of the range where brotli already beats gzip at a comparable CPU cost. + public const int DefaultQuality = 6; + + /// The brotli sliding window size, as the base-2 logarithm of its size in bytes (the library default). + private const int WindowBits = 22; + + private readonly int _quality; + + /// Creates a brotli compressor. + /// The compression level, from (the fastest) to (the best compression). + /// is outside the valid range. + public BrotliHttpCompressor(int quality = DefaultQuality) + { + ArgumentOutOfRangeException.ThrowIfLessThan(quality, MinQuality); + ArgumentOutOfRangeException.ThrowIfGreaterThan(quality, MaxQuality); + _quality = quality; + } + + /// + public override string ContentEncoding => "br"; + + /// + public override Task CompressAsync(byte[] data, CancellationToken cancellationToken = default) + => Task.Run(() => Compress(data), cancellationToken); + + private byte[] Compress(byte[] data) + { + var buffer = new byte[BrotliEncoder.GetMaxCompressedLength(data.Length)]; + if (!BrotliEncoder.TryCompress(data, buffer, out var written, _quality, WindowBits)) + { + throw new InvalidOperationException("brotli compression failed"); + } + + return buffer.AsSpan(0, written).ToArray(); + } +} diff --git a/src/Apify.Client/Http/GzipHttpCompressor.cs b/src/Apify.Client/Http/GzipHttpCompressor.cs new file mode 100644 index 0000000..c87c37e --- /dev/null +++ b/src/Apify.Client/Http/GzipHttpCompressor.cs @@ -0,0 +1,51 @@ +using System; +using System.IO; +using System.IO.Compression; +using System.Threading; +using System.Threading.Tasks; + +namespace Apify.Client.Http; + +/// +/// Compresses request bodies with gzip (Content-Encoding: gzip). Matches the reference client's +/// GzipHttpCompressor. +/// +public sealed class GzipHttpCompressor : HttpCompressor +{ + private readonly CompressionLevel _level; + + /// 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. + /// + /// is not a defined . + public GzipHttpCompressor(CompressionLevel level = CompressionLevel.Optimal) + { + if (!Enum.IsDefined(level)) + { + throw new ArgumentOutOfRangeException(nameof(level), level, "the compression level is not a defined CompressionLevel"); + } + + _level = level; + } + + /// + public override string ContentEncoding => "gzip"; + + /// + public override Task CompressAsync(byte[] data, CancellationToken cancellationToken = default) + => Task.Run(() => Compress(data), cancellationToken); + + private byte[] Compress(byte[] data) + { + using var output = new MemoryStream(); + using (var gzip = new GZipStream(output, _level)) + { + gzip.Write(data, 0, data.Length); + } + + return output.ToArray(); + } +} diff --git a/src/Apify.Client/Http/HttpCompressor.cs b/src/Apify.Client/Http/HttpCompressor.cs new file mode 100644 index 0000000..53d2bd5 --- /dev/null +++ b/src/Apify.Client/Http/HttpCompressor.cs @@ -0,0 +1,27 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace Apify.Client.Http; + +/// +/// 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. +/// +public abstract class HttpCompressor +{ + /// The value sent in the Content-Encoding header, for example gzip or br. + public abstract string ContentEncoding { get; } + + /// Compresses a request body. + /// The raw bytes to compress. + /// A token to cancel the compression. + /// The compressed bytes. + public abstract Task CompressAsync(byte[] data, CancellationToken cancellationToken = default); +} diff --git a/src/Apify.Client/Internal/HttpClientCore.cs b/src/Apify.Client/Internal/HttpClientCore.cs index 9bb4858..43ce7f6 100644 --- a/src/Apify.Client/Internal/HttpClientCore.cs +++ b/src/Apify.Client/Internal/HttpClientCore.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Globalization; using System.IO; -using System.IO.Compression; using System.Net.Http; using System.Net.Http.Headers; using System.Text; @@ -39,12 +38,6 @@ internal sealed class HttpClientCore /// private const int MinCompressBytes = 1024; - /// The Content-Encoding token used for brotli-compressed request bodies. - private const string BrotliEncoding = "br"; - - /// The Content-Encoding token used for gzip-compressed request bodies. - private const string GzipEncoding = "gzip"; - /// Media type prefixes whose payloads carry their own compression (e.g. image/png). private static readonly string[] AlreadyCompressedMediaTypePrefixes = { "audio/", "image/", "video/" }; @@ -99,25 +92,53 @@ internal sealed class HttpClientCore private readonly IHttpTransport _transport; private readonly string? _token; private readonly RetryConfig _retry; - private readonly RequestCompression _compression; + private readonly HttpCompressor _compressor; + private readonly IReadOnlyDictionary? _defaultHeaders; + private readonly Statistics _stats; public HttpClientCore( IHttpTransport transport, string? token, string userAgent, RetryConfig retry, - RequestCompression compression) + HttpCompressor compressor, + Statistics stats, + IReadOnlyDictionary? defaultHeaders = null) { _transport = transport; _token = token; - UserAgent = userAgent; _retry = retry; - _compression = compression; + _compressor = compressor; + _stats = stats; + ValidateDefaultHeaders(defaultHeaders); + _defaultHeaders = defaultHeaders; + UserAgent = FindHeader(defaultHeaders, "User-Agent") ?? userAgent; } - /// The User-Agent header value this client sends. + /// 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). public string UserAgent { get; } + private static string? FindHeader(IReadOnlyDictionary? headers, string name) + { + if (headers is null) + { + return null; + } + + foreach (var header in headers) + { + if (string.Equals(header.Key, name, StringComparison.OrdinalIgnoreCase)) + { + return header.Value; + } + } + + return null; + } + /// The configured overall per-request timeout budget, in seconds. public double RequestTimeoutSecs => _retry.TimeoutSecs; @@ -138,6 +159,7 @@ public async Task CallAsync( string? explicitContentEncoding = null, CancellationToken cancellationToken = default) { + _stats.AddCall(); var delayMillis = _retry.MinDelayMillis; var maxAttempts = _retry.MaxRetries + 1; var path = ExtractPath(url); @@ -154,7 +176,7 @@ public async Task CallAsync( // skips this: the bytes are already in their final, encoded form 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) - : PrepareBody(body, bodyBytes, contentType); + : await PrepareBodyAsync(body, bodyBytes, contentType, cancellationToken).ConfigureAwait(false); Exception? lastError = null; for (var attempt = 1; attempt <= maxAttempts; attempt++) @@ -162,6 +184,7 @@ public async Task CallAsync( bool retryable; try { + _stats.AddRequest(); var response = await SendOnceAsync( method, url, prepared, extraHeaders, AttemptTimeout(baseTimeout, overallCap, attempt), cancellationToken).ConfigureAwait(false); @@ -175,6 +198,11 @@ public async Task CallAsync( return response; } + if (status == RateLimitExceeded) + { + _stats.AddRateLimitError(attempt); + } + var errorBody = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); response.Dispose(); lastError = BuildApiError(status, errorBody, attempt, method.Method, path); @@ -204,8 +232,10 @@ await Task.Delay(TimeSpan.FromMilliseconds(RandomizedDelayMillis(delayMillis)), /// Opens a live streaming response (single attempt, no retry). Used by log streaming. public Task StreamAsync(string url, CancellationToken cancellationToken) { + _stats.AddCall(); + _stats.AddRequest(); var request = BuildRequest(HttpMethod.Get, url, default, null); - return _transport.SendAsync(request, TimeSpan.FromSeconds(_retry.TimeoutSecs), streaming: true, cancellationToken); + return _transport.SendAsync(request, TimeSpan.FromSeconds(Math.Min(_retry.LongTimeoutSecs, _retry.TimeoutSecs)), streaming: true, cancellationToken); } private async Task SendOnceAsync( @@ -220,6 +250,50 @@ private async Task SendOnceAsync( return await _transport.SendAsync(request, timeout, streaming: false, cancellationToken).ConfigureAwait(false); } + /// + /// 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. + /// + /// A header name is a content header or not a valid header name. + private static void ValidateDefaultHeaders(IReadOnlyDictionary? headers) + { + if (headers is null) + { + return; + } + + using var probe = new HttpRequestMessage(); + using var contentProbe = new ByteArrayContent(Array.Empty()); + foreach (var name in headers.Keys) + { + if (probe.Headers.TryAddWithoutValidation(name, string.Empty)) + { + continue; + } + + var reason = contentProbe.Headers.TryAddWithoutValidation(name, string.Empty) + ? "it is a content header, which the client sets per request" + : "it is not a valid HTTP header name"; + throw new ArgumentException($"ApifyClientOptions.Headers: '{name}' cannot be a default header: {reason}"); + } + } + + /// Sets each header on the request, replacing any value already set under the same name. + private static void SetHeaders(HttpRequestMessage request, IReadOnlyDictionary? headers) + { + if (headers is null) + { + return; + } + + foreach (var header in headers) + { + request.Headers.Remove(header.Key); + request.Headers.TryAddWithoutValidation(header.Key, header.Value); + } + } + /// Builds a fully-prepared request with auth, User-Agent, content type and extra headers. private HttpRequestMessage BuildRequest( HttpMethod method, @@ -234,13 +308,10 @@ private HttpRequestMessage BuildRequest( request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _token); } - if (extraHeaders is not null) - { - foreach (var header in extraHeaders) - { - request.Headers.TryAddWithoutValidation(header.Key, header.Value); - } - } + // The client-wide default headers win over the built-in ones above, and a header set for this single call + // wins over both. + SetHeaders(request, _defaultHeaders); + SetHeaders(request, extraHeaders); if (prepared.Bytes is not null) { @@ -267,14 +338,11 @@ private HttpRequestMessage BuildRequest( /// the size threshold (see the remarks). /// /// - /// Bodies at or above are compressed with the configured - /// algorithm: brotli (Content-Encoding: br) by default, matching - /// the reference client's preference, or gzip (Content-Encoding: gzip) when - /// is selected. Both encodings match the reference client's, though - /// the reference only reaches gzip when brotli is unavailable; since .NET always ships brotli, gzip here - /// is a .NET-only opt-in. + /// 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. /// - private PreparedBody PrepareBody(string? body, byte[]? bodyBytes, string contentType) + private async Task PrepareBodyAsync(string? body, byte[]? bodyBytes, string contentType, CancellationToken cancellationToken) { var raw = bodyBytes ?? (body is not null ? Encoding.UTF8.GetBytes(body) : null); if (raw is null) @@ -287,9 +355,8 @@ private PreparedBody PrepareBody(string? body, byte[]? bodyBytes, string content return new PreparedBody(raw, contentType, null); } - return _compression == RequestCompression.Gzip - ? new PreparedBody(GzipCompress(raw), contentType, GzipEncoding) - : new PreparedBody(BrotliCompress(raw), contentType, BrotliEncoding); + var compressed = await _compressor.CompressAsync(raw, cancellationToken).ConfigureAwait(false); + return new PreparedBody(compressed, contentType, _compressor.ContentEncoding); } /// @@ -347,30 +414,6 @@ internal static bool IsCompressibleContentType(string? contentType) return true; } - /// Brotli-compresses a payload into a self-contained byte array. - private static byte[] BrotliCompress(byte[] data) - { - using var output = new MemoryStream(); - using (var brotli = new BrotliStream(output, CompressionMode.Compress)) - { - brotli.Write(data, 0, data.Length); - } - - return output.ToArray(); - } - - /// Gzip-compresses a payload into a self-contained byte array. - private static byte[] GzipCompress(byte[] data) - { - using var output = new MemoryStream(); - using (var gzip = new GZipStream(output, CompressionMode.Compress)) - { - gzip.Write(data, 0, data.Length); - } - - return output.ToArray(); - } - /// /// A request body normalized to bytes, together with the content type and optional /// Content-Encoding to send. A value carries no body. diff --git a/src/Apify.Client/Internal/Json.cs b/src/Apify.Client/Internal/Json.cs index 89e0764..4b4288f 100644 --- a/src/Apify.Client/Internal/Json.cs +++ b/src/Apify.Client/Internal/Json.cs @@ -21,6 +21,12 @@ internal static class Json /// Serializes a value to a JSON string. public static string Encode(object? value) => JsonSerializer.Serialize(value, SerializerOptions); + /// Serializes a value to UTF-8 encoded JSON bytes. + public static byte[] EncodeToUtf8(object? value) => JsonSerializer.SerializeToUtf8Bytes(value, SerializerOptions); + + /// Serializes a value to a (null for a null value). + public static JsonNode? ToNode(object? value) => JsonSerializer.SerializeToNode(value, SerializerOptions); + /// Serializes a to a JSON string. public static string Encode(JsonNode? value) => value?.ToJsonString(SerializerOptions) ?? "null"; diff --git a/src/Apify.Client/Internal/ResourceContext.cs b/src/Apify.Client/Internal/ResourceContext.cs index 2f59647..6280764 100644 --- a/src/Apify.Client/Internal/ResourceContext.cs +++ b/src/Apify.Client/Internal/ResourceContext.cs @@ -19,6 +19,12 @@ internal sealed class ResourceContext public const string ContentTypeJson = "application/json"; public const string ContentTypeJsonCharset = "application/json; charset=utf-8"; + /// The URL path segment that refers to the current directory; never a valid resource id or key. + private const string CurrentDirectorySegment = "."; + + /// The URL path segment that refers to the parent directory; never a valid resource id or key. + private const string ParentDirectorySegment = ".."; + /// How long to wait between polls while waiting for a run/build to finish, in seconds. private const double WaitPollIntervalSecs = 0.25; @@ -87,8 +93,8 @@ public static ResourceContext Collection(HttpClientCore http, string baseUrl, st } /// Creates a context for a single resource: {base}/{resourcePath}/{safeId}. - public static ResourceContext Single(HttpClientCore http, string baseUrl, string resourcePath, string id) - => new(http, baseUrl + "/" + resourcePath + "/" + ToSafeId(id), baseUrl); + public static ResourceContext Single(HttpClientCore http, string baseUrl, string resourcePath, string id, string paramName = "id") + => 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, @@ -567,20 +573,38 @@ public async Task WaitForFinishAsync(int? waitSecs, string resourceN // ---- URL / id helpers ----------------------------------------------------- /// - /// Encodes a resource id so it is safe to embed in a URL path. Apify uses the - /// username~resourcename form, so the first / of an id is replaced with ~. + /// 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. /// - public static string ToSafeId(string id) - { - var slash = id.IndexOf('/', StringComparison.Ordinal); - return slash < 0 ? id : string.Concat(id.AsSpan(0, slash), "~", id.AsSpan(slash + 1)); - } + /// The raw resource id. + /// The name of the caller's parameter, reported by the argument exception. + /// The id is empty or a dot segment. + 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 (record keys, - /// request IDs) cannot break out of the segment. + /// 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 ... /// - public static string EncodePathSegment(string input) => Uri.EscapeDataString(input); + /// The raw, caller-supplied path segment. + /// The name of the caller's parameter, reported by the argument exception. + /// The segment is empty or a dot segment. + public static string EncodePathSegment(string input, string paramName) + { + if (input.Length == 0) + { + throw new ArgumentException("URL path segment must not be empty", paramName); + } + + if (input is CurrentDirectorySegment or ParentDirectorySegment) + { + throw new ArgumentException("URL path segment must not be a dot segment", paramName); + } + + return Uri.EscapeDataString(input); + } /// Extracts the origin (scheme://host[:port]) from a URL, dropping any path. public static string OriginOf(string rawUrl) diff --git a/src/Apify.Client/Internal/ResponseSchemas.json b/src/Apify.Client/Internal/ResponseSchemas.json index c09752b..427b8f8 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-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}/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/ListOfWebhooksResponse"},"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/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}":{"$ref":"#/components/schemas/UserPublicInfoResponse"},"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/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/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/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"]},"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"]},"isEnabled":{"type":["boolean","null"]},"maxRequestsPerActorRun":{"type":["integer","null"]},"memoryMbytes":{"type":["integer","null"]},"shouldPassActorInput":{"type":["boolean","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":{"buildTag":{"type":["string","null"]},"gitRepoUrl":{"type":"string"},"sourceFiles":{"items":{"$ref":"#/components/schemas/SourceCodeFile"},"type":"array"},"sourceType":{"$ref":"#/components/schemas/VersionSourceType"},"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"},"finishedAt":{"format":"date-time","type":["string","null"]},"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","usageTotalUsd","buildNumber"],"type":"object"},"BuildMeta":{"properties":{"clientIp":{"type":["string"]},"origin":{"$ref":"#/components/schemas/RunOrigin"},"userAgent":{"type":["string"]}},"required":["origin"],"type":"object"},"BuildOptions":{"properties":{"betaPackages":{"type":["boolean","null"]},"diskMbytes":{"type":["integer","null"]},"memoryMbytes":{"type":["integer","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"]},"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"]},"pricingModel":{"type":"string"},"pricingPerEvent":{"type":["object","null"]},"reasonForChange":{"type":["string","null"]},"startedAt":{"format":"date-time","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"},"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"},"stats":{"$ref":"#/components/schemas/DatasetStats"},"title":{"type":["string","null"]},"userId":{"type":"string"},"username":{"type":"string"}},"required":["id","name","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"},"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"}]},"maxItems":{"type":["integer","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"}},"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"},"HadMultipleClients":{"type":"boolean"},"HandledRequestCount":{"minimum":0,"type":"integer"},"HeadLimit":{"type":"integer"},"HttpMethod":{"type":"string"},"KeyValueStoreKey":{"properties":{"key":{"type":"string"},"recordPublicUrl":{"format":"uri","type":"string"},"size":{"type":"integer"}},"required":["key","size","recordPublicUrl"],"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"},"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"},"maxMonthlyUsageUsd":{"type":"number"},"maxScheduleCount":{"type":"integer"},"maxTeamAccountSeatCount":{"type":"integer"}},"required":["maxMonthlyUsageUsd","maxMonthlyActorComputeUnits","maxMonthlyExternalDataTransferGbytes","maxMonthlyProxySerps","maxMonthlyResidentialProxyGbytes","maxActorMemoryGbytes","maxActorCount","maxActorTaskCount","maxConcurrentActorJobs","maxTeamAccountSeatCount","dataRetentionDays"],"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"},"ListOfKeyValueStores":{"allOf":[{"$ref":"#/components/schemas/PaginationResponse"},{"properties":{"items":{"items":{"$ref":"#/components/schemas/KeyValueStoreResource"},"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"},"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":{"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"]},"name":{"type":"string"}},"required":["name","description","availableCount"],"type":"object"},"ProxyResource":{"properties":{"groups":{"items":{"$ref":"#/components/schemas/ProxyGroup"},"type":"array"},"password":{"type":"string"}},"required":["password","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"},"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"},"pendingRequestCount":{"$ref":"#/components/schemas/PendingRequestCount"},"stats":{"$ref":"#/components/schemas/RequestQueueStats"},"totalRequestCount":{"$ref":"#/components/schemas/TotalRequestCount"},"userId":{"$ref":"#/components/schemas/QueueUserId"},"username":{"type":"string"}},"required":["id","name","userId","username","createdAt","modifiedAt","accessedAt","totalRequestCount","handledRequestCount","pendingRequestCount","hadMultipleClients"],"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"},"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"}}}]},"RequestResponse":{"properties":{"data":{"$ref":"#/components/schemas/RequestResource"}},"required":["data"],"type":"object"},"RequestUrl":{"type":"string"},"RequestUserData":{"type":"object"},"RetryCount":{"type":"integer"},"Run":{"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","null"]},"chargedEventCounts":{"type":"object"},"containerUrl":{"format":"uri","type":"string"},"defaultDatasetId":{"type":"string"},"defaultKeyValueStoreId":{"type":"string"},"defaultRequestQueueId":{"type":"string"},"exitCode":{"type":["integer","null"]},"finishedAt":{"format":"date-time","type":["string","null"]},"generalAccess":{"anyOf":[{"$ref":"#/components/schemas/GeneralAccess"},{"type":"null"}]},"gitBranchName":{"type":["string","null"]},"id":{"type":"string"},"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"},"platformUsageBillingModel":{"type":"string"},"pricingInfo":{"$ref":"#/components/schemas/ActorRunPricingInfo"},"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"},"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":{"clientIp":{"type":["string","null"]},"origin":{"$ref":"#/components/schemas/RunOrigin"},"scheduleId":{"type":["string","null"]},"scheduledAt":{"format":"date-time","type":["string","null"]},"userAgent":{"type":["string","null"]}},"required":["origin"],"type":"object"},"RunMetamorphEvent":{"properties":{"actorId":{"type":"string"},"buildId":{"type":"string"},"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"},"maxItems":{"minimum":0,"type":["integer","null"]},"maxTotalChargeUsd":{"minimum":0,"type":["number","null"]},"memoryMbytes":{"maximum":32768,"minimum":128,"type":"integer"},"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"},"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"]},"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"]},"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"]},"notifications":{"$ref":"#/components/schemas/ScheduleNotifications"},"title":{"type":["string","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"}},"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"},"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"},"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"},"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"},"modifiedAt":{"format":"date-time","type":"string"},"name":{"type":"string"},"stats":{"anyOf":[{"$ref":"#/components/schemas/TaskStats"},{"type":"null"}]},"userId":{"type":"string"},"username":{"type":["string","null"]}},"required":["id","userId","actId","name","createdAt","modifiedAt"],"type":"object"},"TaskOptions":{"properties":{"build":{"type":["string","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":{"datasetName":{"type":["string","null"]},"datasetView":{"type":["string","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":{"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"},"id":{"type":"string"},"isEnabled":{"type":"boolean"},"maxActorCount":{"type":"integer"},"maxActorMemoryGbytes":{"type":"number"},"maxActorTaskCount":{"type":"integer"},"maxConcurrentActorRuns":{"type":"integer"},"maxMonthlyActorComputeUnits":{"type":"number"},"maxMonthlyExternalDataTransferGbytes":{"type":"number"},"maxMonthlyProxySerps":{"type":"integer"},"maxMonthlyResidentialProxyGbytes":{"type":"number"},"maxMonthlyUsageUsd":{"type":"number"},"maxScheduleCount":{"type":"integer"},"monthlyBasePriceUsd":{"type":"number"},"monthlyUsageCreditsUsd":{"type":"number"},"planPricing":{"type":"object"},"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"},"id":{"type":"string"},"isPaying":{"type":"boolean"},"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":{"applyEnvVarsToBuild":{"type":["boolean","null"]},"buildTag":{"type":["string","null"]},"envVars":{"anyOf":[{"items":{"$ref":"#/components/schemas/EnvVar"},"type":"array"},{"type":"null"}]},"gitHubGistUrl":{"type":["string","null"]},"gitRepoUrl":{"type":["string","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"},"WebhookDispatch":{"properties":{"calls":{"items":{"properties":{"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":{"actionType":{"type":"string"},"condition":{"$ref":"#/components/schemas/WebhookCondition"},"isAdHoc":{"type":"boolean"},"requestUrl":{"format":"uri","type":["string","null"]}},"type":"object"},"WebhookEventType":{"type":"string"},"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"},"doNotRetry":{"type":"boolean"},"eventTypes":{"items":{"$ref":"#/components/schemas/WebhookEventType"},"type":"array"},"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"},"shouldInterpolateStrings":{"type":["boolean","null"]},"stats":{"anyOf":[{"$ref":"#/components/schemas/WebhookStats"},{"type":"null"}]},"userId":{"type":"string"}},"required":["id","createdAt","modifiedAt","userId","eventTypes","condition","ignoreSslErrors","doNotRetry","requestUrl"],"type":"object"},"WebhookResource":{"properties":{"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"},"headersTemplate":{"type":["string","null"]},"id":{"type":"string"},"ignoreSslErrors":{"type":"boolean"},"isAdHoc":{"type":["boolean","null"]},"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"]},"stats":{"anyOf":[{"$ref":"#/components/schemas/WebhookStats"},{"type":"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"},"WebhookStats":{"properties":{"totalDispatches":{"type":"integer"}},"type":"object"}},"specVersion":"v2-2026-10-26T112451Z"} +{"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":{"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":{"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-10-29T100357Z"} diff --git a/src/Apify.Client/Internal/RetryConfig.cs b/src/Apify.Client/Internal/RetryConfig.cs index 4035a8d..0a78b1e 100644 --- a/src/Apify.Client/Internal/RetryConfig.cs +++ b/src/Apify.Client/Internal/RetryConfig.cs @@ -11,17 +11,18 @@ internal sealed class RetryConfig /// 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, and the base duration of the tier (the reference client's - /// timeoutLongSecs and timeoutMaxSecs default to the same value, 360s, for the same reason). + /// to (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. - public RetryConfig(int maxRetries, double minDelayMillis, double maxDelayMillis, double timeoutSecs, double shortTimeoutSecs, double mediumTimeoutSecs) + public RetryConfig(int maxRetries, double minDelayMillis, double maxDelayMillis, double timeoutSecs, double longTimeoutSecs, double shortTimeoutSecs, double mediumTimeoutSecs) { MaxRetries = maxRetries; MinDelayMillis = minDelayMillis; MaxDelayMillis = maxDelayMillis; TimeoutSecs = timeoutSecs; + LongTimeoutSecs = longTimeoutSecs; ShortTimeoutSecs = shortTimeoutSecs; MediumTimeoutSecs = mediumTimeoutSecs; } @@ -37,10 +38,13 @@ public RetryConfig(int maxRetries, double minDelayMillis, double maxDelayMillis, /// /// Overall per-request timeout budget, in seconds (every attempt's growing timeout is capped here, - /// regardless of tier); also the base duration of the tier. + /// regardless of tier). /// public double TimeoutSecs { get; } + /// Base duration of the tier, in seconds. + public double LongTimeoutSecs { get; } + /// Base duration of the tier, in seconds. public double ShortTimeoutSecs { get; } @@ -52,6 +56,6 @@ public RetryConfig(int maxRetries, double minDelayMillis, double maxDelayMillis, { RequestTier.Short => ShortTimeoutSecs, RequestTier.Medium => MediumTimeoutSecs, - _ => TimeoutSecs, + _ => LongTimeoutSecs, }; } diff --git a/src/Apify.Client/Models/ActorRun.cs b/src/Apify.Client/Models/ActorRun.cs index e6c7b30..1487efd 100644 --- a/src/Apify.Client/Models/ActorRun.cs +++ b/src/Apify.Client/Models/ActorRun.cs @@ -60,4 +60,17 @@ public ActorRun(JsonObject data) /// Whether the run has reached a terminal (finished) 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. + /// + public string? ConsoleUrl => GetUrlString("consoleUrl"); + + /// + /// The Server Actor deployment of the run: an object whose deployment names the pool serving the run + /// (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. + /// + public JsonObject? Standby => Get("standby") as JsonObject; } diff --git a/src/Apify.Client/Models/Build.cs b/src/Apify.Client/Models/Build.cs index 63bb34b..fddaced 100644 --- a/src/Apify.Client/Models/Build.cs +++ b/src/Apify.Client/Models/Build.cs @@ -43,4 +43,16 @@ public Build(JsonObject data) /// Whether the build has reached a terminal (finished) status. public bool IsTerminal => Statuses.IsTerminal(Status); + + /// The changelog the Actor's source provided for this build, if any. + public string? Changelog => GetString("changelog"); + + /// The exit code of the build process (0 for a successful build); absent while still building. + public long? ExitCode => GetInt("exitCode"); + + /// The name of the Git branch the build's source was taken from, if it came from a Git repository. + public string? GitBranchName => GetString("gitBranchName"); + + /// 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"); } diff --git a/src/Apify.Client/Models/Dataset.cs b/src/Apify.Client/Models/Dataset.cs index c51aec1..b8b23f5 100644 --- a/src/Apify.Client/Models/Dataset.cs +++ b/src/Apify.Client/Models/Dataset.cs @@ -29,4 +29,7 @@ public Dataset(JsonObject data) /// The number of items currently stored. public long? ItemCount => GetInt("itemCount"); + + /// The human-readable title of the dataset, if one was set. + public string? Title => GetString("title"); } diff --git a/src/Apify.Client/Models/KeyValueStore.cs b/src/Apify.Client/Models/KeyValueStore.cs index 14ab72d..640d97a 100644 --- a/src/Apify.Client/Models/KeyValueStore.cs +++ b/src/Apify.Client/Models/KeyValueStore.cs @@ -26,4 +26,7 @@ public KeyValueStore(JsonObject data) /// When the store was last modified (ISO-8601 string). public string? ModifiedAt => GetString("modifiedAt"); + + /// The human-readable title of the key-value store, if one was set. + public string? Title => GetString("title"); } diff --git a/src/Apify.Client/Models/RequestQueue.cs b/src/Apify.Client/Models/RequestQueue.cs index 662a9c4..a263cc5 100644 --- a/src/Apify.Client/Models/RequestQueue.cs +++ b/src/Apify.Client/Models/RequestQueue.cs @@ -29,4 +29,7 @@ public RequestQueue(JsonObject data) /// The total number of requests ever added. public long? TotalRequestCount => GetInt("totalRequestCount"); + + /// The human-readable title of the request queue, if one was set. + public string? Title => GetString("title"); } diff --git a/src/Apify.Client/Models/RequestQueueRequest.cs b/src/Apify.Client/Models/RequestQueueRequest.cs index 065c53a..9f4628f 100644 --- a/src/Apify.Client/Models/RequestQueueRequest.cs +++ b/src/Apify.Client/Models/RequestQueueRequest.cs @@ -92,6 +92,25 @@ public string? LockExpiresAt set => SetString("lockExpiresAt", value); } + /// + /// The client key of the client that currently holds this request's processing lock. Only present on + /// locked requests returned by . + /// + public string? LockByClient + { + get => GetString("lockByClient"); + set => SetString("lockByClient", value); + } + + /// + /// When this request was handled (ISO-8601 string); null while the request is still pending. + /// + public string? HandledAt + { + get => GetString("handledAt"); + set => SetString("handledAt", value); + } + /// Arbitrary user-attached metadata. public JsonNode? UserData { diff --git a/src/Apify.Client/Models/Schedule.cs b/src/Apify.Client/Models/Schedule.cs index 14d4676..e80a832 100644 --- a/src/Apify.Client/Models/Schedule.cs +++ b/src/Apify.Client/Models/Schedule.cs @@ -26,4 +26,16 @@ public Schedule(JsonObject data) /// Whether the schedule is currently active. public bool? IsEnabled => GetBool("isEnabled"); + + /// The human-readable title of the schedule, if one was set. + public string? Title => GetString("title"); + + /// A description of the schedule, if one was set. + public string? Description => GetString("description"); + + /// + /// The state of the Console's schedule editor, or null once an API update changed the + /// or the timezone and the editor state was dropped. + /// + public JsonObject? UiState => Get("uiState") as JsonObject; } diff --git a/src/Apify.Client/Models/Webhook.cs b/src/Apify.Client/Models/Webhook.cs index 760346d..0627c65 100644 --- a/src/Apify.Client/Models/Webhook.cs +++ b/src/Apify.Client/Models/Webhook.cs @@ -27,4 +27,13 @@ public Webhook(JsonObject data) /// The events that trigger the webhook. public IReadOnlyList? EventTypes => GetStringList("eventTypes"); + + /// The human-readable title of the webhook, if one was set. + public string? Title => GetString("title"); + + /// Whether the webhook is enabled; a disabled webhook is not dispatched. + public bool? IsEnabled => GetBool("isEnabled"); + + /// Whether the webhook was created by an Apify integration rather than directly by the user. + public bool? IsApifyIntegration => GetBool("isApifyIntegration"); } diff --git a/src/Apify.Client/Options/RunListOptions.cs b/src/Apify.Client/Options/RunListOptions.cs index 75c9b6a..2243e20 100644 --- a/src/Apify.Client/Options/RunListOptions.cs +++ b/src/Apify.Client/Options/RunListOptions.cs @@ -5,7 +5,7 @@ namespace Apify.Client.Options; /// /// Run-specific filters for listing runs. The StartedAfter/StartedBefore filters are only -/// honoured by the Actor-scoped and task-scoped run collections. +/// honoured by the account-wide and Actor-scoped run collections, not by task runs. /// public sealed class RunListOptions { @@ -15,6 +15,15 @@ public sealed class RunListOptions /// 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. + /// + public IReadOnlyList? Deployment { get; init; } + /// Filter to runs started after this ISO-8601 timestamp. public string? StartedAfter { get; init; } @@ -24,6 +33,7 @@ public sealed class RunListOptions internal void AppendTo(QueryParams q) { q.AddCsv("status", Status) + .AddCsv("deployment", Deployment) .AddString("startedAfter", StartedAfter) .AddString("startedBefore", StartedBefore); } diff --git a/src/Apify.Client/Options/StoreListOptions.cs b/src/Apify.Client/Options/StoreListOptions.cs index a7a1f64..f957b1b 100644 --- a/src/Apify.Client/Options/StoreListOptions.cs +++ b/src/Apify.Client/Options/StoreListOptions.cs @@ -1,3 +1,4 @@ +using System; using Apify.Client.Internal; namespace Apify.Client.Options; @@ -42,9 +43,23 @@ public sealed class StoreListOptions /// Filter to Actors that allow agentic users. 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. + /// + public bool? IncludeInputSchema { get; init; } + + /// The API's maximum page size while is set. + public const int MaxPageLimitWithInputSchema = 10; + /// The response format (full, agent). public string? ResponseFormat { get; init; } + /// The page size iteration requests: the caller's , capped at the API's maximum while the input schema is included. + internal int? IterationChunkSize() + => IncludeInputSchema is true ? Math.Min(ChunkSize ?? MaxPageLimitWithInputSchema, MaxPageLimitWithInputSchema) : ChunkSize; + internal void AppendTo(QueryParams q) { q.AddInt("offset", Offset) @@ -56,6 +71,7 @@ internal void AppendTo(QueryParams q) .AddString("pricingModel", PricingModel) .AddBool("includeUnrunnableActors", IncludeUnrunnableActors) .AddBool("allowsAgenticUsers", AllowsAgenticUsers) + .AddBool("includeInputSchema", IncludeInputSchema) .AddString("responseFormat", ResponseFormat); } } diff --git a/src/Apify.Client/RequestCompression.cs b/src/Apify.Client/RequestCompression.cs index df20def..880626c 100644 --- a/src/Apify.Client/RequestCompression.cs +++ b/src/Apify.Client/RequestCompression.cs @@ -4,10 +4,9 @@ namespace Apify.Client; /// Algorithm used to compress large request bodies before sending them. /// /// -/// The reference client prefers brotli and only falls back to gzip on runtimes where brotli is -/// unavailable. .NET always ships brotli, so instead of an automatic fallback this option lets callers -/// choose the algorithm explicitly, keeping both code paths genuinely selectable. Regardless of the -/// choice, only string/byte payloads at or above the compression threshold are compressed. +/// 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. /// public enum RequestCompression { diff --git a/src/Apify.Client/Resources/ActorClient.cs b/src/Apify.Client/Resources/ActorClient.cs index 7268942..016d721 100644 --- a/src/Apify.Client/Resources/ActorClient.cs +++ b/src/Apify.Client/Resources/ActorClient.cs @@ -90,8 +90,10 @@ private async Task StartOnceAsync(object? input, ActorStartOptions opt /// 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. null disables - /// redirection. + /// 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. /// /// A token to cancel the request. public async Task CallAsync( diff --git a/src/Apify.Client/Resources/ActorEnvVarClient.cs b/src/Apify.Client/Resources/ActorEnvVarClient.cs index b50d52f..43d121b 100644 --- a/src/Apify.Client/Resources/ActorEnvVarClient.cs +++ b/src/Apify.Client/Resources/ActorEnvVarClient.cs @@ -17,7 +17,7 @@ public sealed class ActorEnvVarClient internal ActorEnvVarClient(HttpClientCore http, string versionUrl, string name) { - _ctx = ResourceContext.Single(http, versionUrl, "env-vars", name); + _ctx = ResourceContext.Single(http, versionUrl, "env-vars", name, nameof(name)); } /// Fetches the environment variable, or null if it does not exist. diff --git a/src/Apify.Client/Resources/ActorVersionClient.cs b/src/Apify.Client/Resources/ActorVersionClient.cs index b2d4d7b..012db24 100644 --- a/src/Apify.Client/Resources/ActorVersionClient.cs +++ b/src/Apify.Client/Resources/ActorVersionClient.cs @@ -20,7 +20,7 @@ public sealed class ActorVersionClient internal ActorVersionClient(HttpClientCore http, string actorUrl, string versionNumber) { _http = http; - _ctx = ResourceContext.Single(http, actorUrl, "versions", versionNumber); + _ctx = ResourceContext.Single(http, actorUrl, "versions", versionNumber, nameof(versionNumber)); _versionUrl = _ctx.SubUrl(""); } diff --git a/src/Apify.Client/Resources/DatasetClient.cs b/src/Apify.Client/Resources/DatasetClient.cs index ea41489..e2dc9b8 100644 --- a/src/Apify.Client/Resources/DatasetClient.cs +++ b/src/Apify.Client/Resources/DatasetClient.cs @@ -2,11 +2,13 @@ using System.Collections.Generic; using System.Globalization; using System.IO; +using System.Linq; using System.Net.Http; using System.Runtime.CompilerServices; +using System.Text.Json; +using System.Text.Json.Nodes; using System.Threading; using System.Threading.Tasks; -using System.Text.Json.Nodes; using Apify.Client.Internal; using Apify.Client.Models; using Apify.Client.Options; @@ -204,21 +206,79 @@ public async Task StreamItemsAsync(DownloadItemsFormat format, DatasetDo } /// Pushes one or more items to the dataset. - /// Must serialize to a JSON object or an array of objects. + /// + /// 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. + /// + /// A single item, an array of items, or the JSON serialization of either. /// A token to cancel the request. + /// serializes to anything but a JSON object or an array of objects. public async Task PushItemsAsync(object items, CancellationToken cancellationToken = default) { + var body = items as string; + var bodyBytes = body is null ? SerializeItems(items) : null; var url = _ctx.MergedParams(new QueryParams()).ApplyToUrl(_ctx.SubUrl("items")); using var response = await _http.CallAsync( HttpMethod.Post, url, - Json.Encode(items), + body, ResourceContext.ContentTypeJsonCharset, timeout: _ctx.RequestTimeout, + doNotRetryTimeouts: true, + bodyBytes: bodyBytes, tier: RequestTier.Medium, cancellationToken: cancellationToken).ConfigureAwait(false); } + /// + /// 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. + /// + private static byte[] SerializeItems(object items) + { + var bytes = Json.EncodeToUtf8(items); + if (!IsObjectOrArrayOfObjects(bytes)) + { + throw new ArgumentException("items must serialize to a JSON object or an array of JSON objects", nameof(items)); + } + + return bytes; + } + + private static bool IsObjectOrArrayOfObjects(ReadOnlySpan json) + { + var reader = new Utf8JsonReader(json); + if (!reader.Read()) + { + return false; + } + + if (reader.TokenType == JsonTokenType.StartObject) + { + return true; + } + + if (reader.TokenType != JsonTokenType.StartArray) + { + return false; + } + + while (reader.Read() && reader.TokenType != JsonTokenType.EndArray) + { + if (reader.TokenType != JsonTokenType.StartObject) + { + return false; + } + + reader.Skip(); + } + + return true; + } + /// Returns statistical information about the dataset. /// /// Throws if the dataset does not exist (no longer resolves to null): the statistics endpoint has diff --git a/src/Apify.Client/Resources/KeyValueStoreClient.cs b/src/Apify.Client/Resources/KeyValueStoreClient.cs index 8b3c3a2..f9de3a3 100644 --- a/src/Apify.Client/Resources/KeyValueStoreClient.cs +++ b/src/Apify.Client/Resources/KeyValueStoreClient.cs @@ -155,12 +155,18 @@ public async IAsyncEnumerable IterateKeysAsync( /// The record key. /// A token to cancel the request. public Task RecordExistsAsync(string key, CancellationToken cancellationToken = default) - => _ctx.HeadExistsAsync("records/" + ResourceContext.EncodePathSegment(key), new QueryParams(), RequestTier.Long, cancellationToken); + => _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. /// + /// + /// 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 record key. /// Optional fetch options. /// A token to cancel the request. @@ -169,7 +175,7 @@ public Task RecordExistsAsync(string key, CancellationToken cancellationTo options ??= new GetRecordOptions { Attachment = true }; var q = new QueryParams(); options.AppendTo(q); - var url = _ctx.MergedParams(q).ApplyToUrl(_ctx.SubUrl("records/" + ResourceContext.EncodePathSegment(key))); + var url = _ctx.MergedParams(q).ApplyToUrl(_ctx.SubUrl("records/" + ResourceContext.EncodePathSegment(key, nameof(key)))); try { using var response = await _http.CallAsync(HttpMethod.Get, url, timeout: _ctx.RequestTimeout, tier: RequestTier.Long, cancellationToken: cancellationToken).ConfigureAwait(false); @@ -219,7 +225,7 @@ public Task SetRecordAsync(string key, byte[] value, string contentType, SetReco } return _ctx.PutRawAsync( - "records/" + ResourceContext.EncodePathSegment(key), + "records/" + ResourceContext.EncodePathSegment(key, nameof(key)), new QueryParams(), value, contentType, @@ -241,7 +247,7 @@ public Task SetRecordJsonAsync(string key, object? value, CancellationToken canc /// The record key. /// A token to cancel the request. public Task DeleteRecordAsync(string key, CancellationToken cancellationToken = default) - => _ctx.DeleteResourceAsync("records/" + ResourceContext.EncodePathSegment(key), RequestTier.Short, 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 @@ -263,7 +269,7 @@ public async Task GetRecordPublicUrlAsync(string key, CancellationToken } } - return q.ApplyToUrl(_ctx.PublicUrl("records/" + ResourceContext.EncodePathSegment(key))); + return q.ApplyToUrl(_ctx.PublicUrl("records/" + ResourceContext.EncodePathSegment(key, nameof(key)))); } /// diff --git a/src/Apify.Client/Resources/LogClient.cs b/src/Apify.Client/Resources/LogClient.cs index dec89a2..3f53f0a 100644 --- a/src/Apify.Client/Resources/LogClient.cs +++ b/src/Apify.Client/Resources/LogClient.cs @@ -27,7 +27,7 @@ private LogClient(HttpClientCore http, ResourceContext ctx, bool ambiguousNotFou } internal static LogClient ForId(HttpClientCore http, string baseUrl, string id) - => new(http, ResourceContext.Single(http, baseUrl, "logs", id), ambiguousNotFound: false); + => 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(), diff --git a/src/Apify.Client/Resources/RequestQueueClient.cs b/src/Apify.Client/Resources/RequestQueueClient.cs index 9036740..b660a05 100644 --- a/src/Apify.Client/Resources/RequestQueueClient.cs +++ b/src/Apify.Client/Resources/RequestQueueClient.cs @@ -21,13 +21,16 @@ public sealed class RequestQueueClient /// /// 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 sliceArrayByByteLength. + /// batch call exceeds this, matching 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. private const double PayloadSafetyBufferPercent = 0.0001; + /// The two bytes of the brackets that wrap a JSON array body, even one holding a single request. + private const int JsonArrayBracketBytes = 2; + private readonly HttpClientCore _http; private readonly ResourceContext _ctx; private readonly string? _clientKey; @@ -112,7 +115,7 @@ public async Task AddRequestAsync(RequestQueueRequest /// A token to cancel the request. public async Task GetRequestAsync(string id, CancellationToken cancellationToken = default) { - var data = await _ctx.GetResourceAsync("requests/" + ResourceContext.EncodePathSegment(id), new QueryParams(), RequestTier.Short, cancellationToken).ConfigureAwait(false); + var data = await _ctx.GetResourceAsync("requests/" + ResourceContext.EncodePathSegment(id, nameof(id)), new QueryParams(), RequestTier.Short, cancellationToken).ConfigureAwait(false); return data is JsonObject obj ? RequestQueueRequest.FromJsonObject(obj) : null; } @@ -125,10 +128,15 @@ public async Task AddRequestAsync(RequestQueueRequest /// A token to cancel the request. public async Task UpdateRequestAsync(RequestQueueRequest request, bool forefront = false, CancellationToken cancellationToken = default) { + if (string.IsNullOrEmpty(request.Id)) + { + throw new ArgumentException("the request to update must have an Id", nameof(request)); + } + var q = new QueryParams(); q.AddBool("forefront", forefront); ApplyClientKey(q); - var url = _ctx.MergedParams(q).ApplyToUrl(_ctx.SubUrl("requests/" + ResourceContext.EncodePathSegment(request.Id ?? string.Empty))); + var url = _ctx.MergedParams(q).ApplyToUrl(_ctx.SubUrl("requests/" + ResourceContext.EncodePathSegment(request.Id, nameof(request)))); using var response = await _http.CallAsync(HttpMethod.Put, url, Json.Encode(request.ToJsonObject()), ResourceContext.ContentTypeJson, _timeout, tier: RequestTier.Medium, cancellationToken: cancellationToken).ConfigureAwait(false); return new RequestQueueOperationInfo(await response.ReadDataObjectAsync(cancellationToken).ConfigureAwait(false)); } @@ -140,7 +148,7 @@ public async Task DeleteRequestAsync(string id, CancellationToken cancellationTo { var q = new QueryParams(); ApplyClientKey(q); - var url = _ctx.MergedParams(q).ApplyToUrl(_ctx.SubUrl("requests/" + ResourceContext.EncodePathSegment(id))); + var url = _ctx.MergedParams(q).ApplyToUrl(_ctx.SubUrl("requests/" + ResourceContext.EncodePathSegment(id, nameof(id)))); try { using var response = await _http.CallAsync(HttpMethod.Delete, url, timeout: _timeout, tier: RequestTier.Short, cancellationToken: cancellationToken).ConfigureAwait(false); @@ -206,18 +214,25 @@ public async Task BatchAddRequestsAsync( var payloadSizeLimitBytes = MaxPayloadSizeBytes - (int)Math.Ceiling(MaxPayloadSizeBytes * PayloadSafetyBufferPercent); - // Pre-compute all chunks up front (bounded first by the count limit of 25, then by payload byte size) - // so they can be dispatched sequentially or with bounded parallelism. - var chunks = new List>(); - var index = 0; - while (index < list.Count) + // 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. + var serialized = new List(list.Count); + for (var i = 0; i < list.Count; i++) { - var countSlice = list.GetRange(index, Math.Min(MaxRequestsPerBatch, list.Count - index)); - var chunk = SliceByByteLength(countSlice, payloadSizeLimitBytes, index); - chunks.Add(chunk); - index += chunk.Count; + var json = Json.Encode(list[i].ToJsonObject()); + var byteLength = Encoding.UTF8.GetByteCount(json); + if (byteLength + JsonArrayBracketBytes > payloadSizeLimitBytes) + { + throw new ArgumentException( + $"BatchAddRequests: the request at index {i} exceeds the maximum payload size ({payloadSizeLimitBytes} bytes)", nameof(requests)); + } + + serialized.Add(new SerializedRequest(list[i], json, byteLength)); } + var chunks = SplitIntoJsonArrayBatches(serialized, payloadSizeLimitBytes); + var merged = new BatchAddResult(); if (chunks.Count == 0) { @@ -255,7 +270,7 @@ public async Task BatchAddRequestsAsync( /// Runs one chunk's add-with-retries under the concurrency gate. private async Task DispatchChunkAsync( - List chunk, + List chunk, bool forefront, BatchAddRequestsOptions options, SemaphoreSlim gate, @@ -272,50 +287,43 @@ private async Task DispatchChunkAsync( } } + /// A request together with its JSON serialization and that serialization's UTF-8 size. + private readonly record struct SerializedRequest(RequestQueueRequest Request, string Json, int ByteLength); + /// - /// Returns the longest leading run of whose combined JSON payload stays - /// under , always keeping at least one request so iteration makes - /// progress. Ports the reference client's sliceArrayByByteLength. + /// 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. /// - private static List SliceByByteLength(List requests, int maxByteLength, int startIndex) + private static List> SplitIntoJsonArrayBatches(List requests, int maxByteLength) { - var payloads = ToPayload(requests); - - if (Encoding.UTF8.GetByteCount(Json.Encode(payloads)) < 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. + var byteLength = 1; + foreach (var request in requests) { - return requests; - } - - var sliced = new List(); - var byteLength = 2; // the two bytes of an empty array "[]" - for (var i = 0; i < requests.Count; i++) - { - var itemBytes = Encoding.UTF8.GetByteCount(Json.Encode(requests[i].ToJsonObject())); - if (itemBytes > maxByteLength) - { - throw new ArgumentException( - $"BatchAddRequests: the request at index {startIndex + i} exceeds the maximum payload size ({maxByteLength} bytes)"); - } - - if (byteLength + itemBytes >= maxByteLength) + if (batch.Count > 0 && (batch.Count >= MaxRequestsPerBatch || byteLength + request.ByteLength + 1 > maxByteLength)) { - break; + batches.Add(batch); + batch = new List(); + byteLength = 1; } - byteLength += itemBytes; - sliced.Add(requests[i]); + batch.Add(request); + byteLength += request.ByteLength + 1; } - // Guarantee forward progress: keep at least the first request (it fits under the hard max). - if (sliced.Count == 0) + if (batch.Count > 0) { - sliced.Add(requests[0]); + batches.Add(batch); } - return sliced; + return batches; } - private async Task BatchAddChunkWithRetriesAsync(List chunk, bool forefront, BatchAddRequestsOptions options, CancellationToken cancellationToken) + private async Task BatchAddChunkWithRetriesAsync(List chunk, bool forefront, BatchAddRequestsOptions options, CancellationToken cancellationToken) { var maxRetries = options.MaxUnprocessedRequestsRetries; var minDelayMillis = options.MinDelayBetweenUnprocessedRequestsRetriesMillis; @@ -337,13 +345,20 @@ 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. 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. + _http.Stats.AddRateLimitError(attempt + 1); + } + remaining = RequestsNotYetProcessed(chunk, processed); if (remaining.Count == 0) { @@ -362,12 +377,12 @@ private async Task BatchAddChunkWithRetriesAsync(List BatchAddChunkAsync(List requests, bool forefront, CancellationToken cancellationToken) + private async Task BatchAddChunkAsync(List requests, bool forefront, CancellationToken cancellationToken) { var q = new QueryParams(); q.AddBool("forefront", forefront); ApplyClientKey(q); - var data = await _ctx.PostWithBodyAsync("requests/batch", q, Json.Encode(ToPayload(requests)), ResourceContext.ContentTypeJson, RequestTier.Medium, cancellationToken).ConfigureAwait(false); + var data = await _ctx.PostWithBodyAsync("requests/batch", q, JoinAsJsonArray(requests), ResourceContext.ContentTypeJson, RequestTier.Medium, cancellationToken).ConfigureAwait(false); var processed = new List(); if (data.TryGetPropertyValue("processedRequests", out var pNode) && pNode is JsonArray pArray) @@ -390,7 +405,11 @@ private async Task BatchAddChunkAsync(List return new BatchAddResult(processed, unprocessed); } - private static List RequestsNotYetProcessed(List chunk, List processed) + /// Joins already-serialized requests into a JSON array body without serializing them again. + private static string JoinAsJsonArray(List requests) + => "[" + string.Join(",", requests.ConvertAll(static serialized => serialized.Json)) + "]"; + + private static List RequestsNotYetProcessed(List chunk, List processed) { var processedKeys = new HashSet(StringComparer.Ordinal); foreach (var info in processed) @@ -401,12 +420,12 @@ private static List RequestsNotYetProcessed(List(); - foreach (var request in chunk) + var remaining = new List(); + foreach (var serialized in chunk) { - if (!processedKeys.Contains(request.UniqueKey ?? string.Empty)) + if (!processedKeys.Contains(serialized.Request.UniqueKey ?? string.Empty)) { - remaining.Add(request); + remaining.Add(serialized); } } @@ -480,7 +499,7 @@ public async Task ProlongRequestLockAsync(string id, int lockSe var q = new QueryParams(); q.AddInt("lockSecs", lockSecs).AddBool("forefront", forefront); ApplyClientKey(q); - var url = _ctx.MergedParams(q).ApplyToUrl(_ctx.SubUrl("requests/" + ResourceContext.EncodePathSegment(id) + "/lock")); + var url = _ctx.MergedParams(q).ApplyToUrl(_ctx.SubUrl("requests/" + ResourceContext.EncodePathSegment(id, nameof(id)) + "/lock")); using var response = await _http.CallAsync(HttpMethod.Put, url, null, "", _timeout, tier: RequestTier.Medium, cancellationToken: cancellationToken).ConfigureAwait(false); return RequestLockInfo.FromData(await response.ReadDataObjectAsync(cancellationToken).ConfigureAwait(false)); } @@ -497,7 +516,7 @@ public async Task DeleteRequestLockAsync(string id, bool forefront = false, Canc var q = new QueryParams(); q.AddBool("forefront", forefront); ApplyClientKey(q); - var url = _ctx.MergedParams(q).ApplyToUrl(_ctx.SubUrl("requests/" + ResourceContext.EncodePathSegment(id) + "/lock")); + var url = _ctx.MergedParams(q).ApplyToUrl(_ctx.SubUrl("requests/" + ResourceContext.EncodePathSegment(id, nameof(id)) + "/lock")); try { using var response = await _http.CallAsync(HttpMethod.Delete, url, timeout: _timeout, tier: RequestTier.Short, cancellationToken: cancellationToken).ConfigureAwait(false); diff --git a/src/Apify.Client/Resources/RunClient.cs b/src/Apify.Client/Resources/RunClient.cs index 185db74..b7d5baf 100644 --- a/src/Apify.Client/Resources/RunClient.cs +++ b/src/Apify.Client/Resources/RunClient.cs @@ -215,6 +215,18 @@ public async Task WaitForFinishAsync(int? waitSecs = null, Cancellatio return new ActorRun(data); } + /// + /// Builds the reference client's default sink: the run and its Actor are fetched for the + /// <Actor name> runId:<id> -> prefix. + /// + private async Task> CreateDefaultSinkAsync(CancellationToken cancellationToken) + { + var run = await GetAsync(cancellationToken: cancellationToken).ConfigureAwait(false); + var actorId = run?.ActId ?? string.Empty; + var actor = actorId.Length == 0 ? null : await _root.Actor(actorId).GetAsync(cancellationToken).ConfigureAwait(false); + 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. @@ -226,6 +238,11 @@ internal async Task WaitForFinishWithLogAsync(int? waitSecs, Action GetStreamedLogAsync(CancellationToken cancellationToke /// (or dispose it) to end. Consistent with the reference client's /// run-log redirection convenience. /// - /// The sink each complete log message is written to. + /// + /// The sink each complete log message is written to, used as it is: unlike in CallAsync, + /// is not resolved here and adds no prefix; use + /// for the reference's <Actor name> runId:<id> -> prefix. + /// /// /// If true (default), redirect the whole log including messages from before this call; if /// false, skip messages older than the moment the helper is created. diff --git a/src/Apify.Client/Resources/StoreCollectionClient.cs b/src/Apify.Client/Resources/StoreCollectionClient.cs index d2cd32d..55ae137 100644 --- a/src/Apify.Client/Resources/StoreCollectionClient.cs +++ b/src/Apify.Client/Resources/StoreCollectionClient.cs @@ -33,14 +33,15 @@ public Task> ListAsync(StoreListOptions? opti /// demand. /// /// Optional listing filters; Offset/Limit bound where iteration - /// starts and the total number of Actors yielded, and ChunkSize sets the page size. + /// starts and the total number of Actors yielded, and ChunkSize sets the page size + /// (at most 10 with IncludeInputSchema). /// A token to cancel the iteration. public IAsyncEnumerable IterateAsync(StoreListOptions? options = null, CancellationToken cancellationToken = default) { options ??= new StoreListOptions(); var q = new QueryParams(); options.AppendTo(q); - return _ctx.IterateListAsync("", q, options.Offset ?? 0, options.Limit, options.ChunkSize, static d => new ActorStoreListItem(d), RequestTier.Medium, cancellationToken); + return _ctx.IterateListAsync("", q, options.Offset ?? 0, options.Limit, options.IterationChunkSize(), static d => new ActorStoreListItem(d), RequestTier.Medium, cancellationToken); } /// diff --git a/src/Apify.Client/Resources/TaskClient.cs b/src/Apify.Client/Resources/TaskClient.cs index 2ab2052..053f414 100644 --- a/src/Apify.Client/Resources/TaskClient.cs +++ b/src/Apify.Client/Resources/TaskClient.cs @@ -118,8 +118,10 @@ private async Task StartOnceAsync(object? input, TaskStartOptions opti /// 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. null disables - /// redirection. + /// 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. /// /// A token to cancel the request. public async Task CallAsync( diff --git a/src/Apify.Client/Resources/UserClient.cs b/src/Apify.Client/Resources/UserClient.cs index d0208bf..9a0514c 100644 --- a/src/Apify.Client/Resources/UserClient.cs +++ b/src/Apify.Client/Resources/UserClient.cs @@ -12,22 +12,20 @@ namespace Apify.Client.Resources; /// A client for accessing user data (/v2/users/{userId} or /v2/users/me). /// /// -/// For the current user (me), it also exposes account usage and limits. Those endpoints only exist -/// for me and throw if called on another user's client. +/// It also exposes the user's monthly usage and account limits. Besides me, the API accepts the account's +/// ID or username, but only for your own account or the organization your API token belongs to. API tokens +/// with limited permissions cannot use these by-ID endpoints (me is not affected by that), and updating +/// an organization's limits also needs the permission to manage the organization. /// public sealed class UserClient { - private const string Me = "me"; - private readonly HttpClientCore _http; private readonly ResourceContext _ctx; - private readonly bool _isMe; internal UserClient(HttpClientCore http, string baseUrl, string id) { _http = http; _ctx = ResourceContext.Single(http, baseUrl, "users", id); - _isMe = id == Me; } /// @@ -43,14 +41,13 @@ internal UserClient(HttpClientCore http, string baseUrl, string id) } /// - /// Fetches the current account's monthly usage for the month containing the given date (formatted as - /// YYYY-MM-DD). An empty/null date reports the current month. Only available for me. + /// Fetches the user's monthly usage for the month containing the given date (formatted as + /// YYYY-MM-DD). An empty/null date reports the current month. /// /// The date whose month to report, or null for the current month. /// A token to cancel the request. public async Task MonthlyUsageAsync(string? date = null, CancellationToken cancellationToken = default) { - RequireMe(); var q = new QueryParams(); if (!string.IsNullOrEmpty(date)) { @@ -60,20 +57,18 @@ public async Task MonthlyUsageAsync(string? date = null, Cancellatio return await _ctx.GetResourceRequiredAsync("usage/monthly", q, RequestTier.Short, cancellationToken).ConfigureAwait(false); } - /// Fetches the current account's resource limits. Only available for me. + /// Fetches the user's account and usage limits. /// A token to cancel the request. public async Task LimitsAsync(CancellationToken cancellationToken = default) { - RequireMe(); return await _ctx.GetResourceRequiredAsync("limits", new QueryParams(), RequestTier.Short, cancellationToken).ConfigureAwait(false); } - /// Updates the current account's resource limits. Only available for me. + /// Updates the user's account limits. /// Any JSON-serializable limits object. /// A token to cancel the request. public async Task UpdateLimitsAsync(object newLimits, CancellationToken cancellationToken = default) { - RequireMe(); using var response = await _http.CallAsync( HttpMethod.Put, _ctx.SubUrl("limits"), @@ -84,14 +79,6 @@ public async Task UpdateLimitsAsync(object newLimits, CancellationToken cancella cancellationToken: cancellationToken).ConfigureAwait(false); } - private void RequireMe() - { - if (!_isMe) - { - throw new System.InvalidOperationException("this operation is only available for the current user (use Me())"); - } - } - /// /// Returns this client with every subsequent call's timeout set to , /// overriding the tier default (see the "Timeout tiers" section of the top-level README). Pass diff --git a/src/Apify.Client/Statistics.cs b/src/Apify.Client/Statistics.cs new file mode 100644 index 0000000..0adc56b --- /dev/null +++ b/src/Apify.Client/Statistics.cs @@ -0,0 +1,55 @@ +using System.Collections.Generic; +using System.Threading; + +namespace Apify.Client; + +/// +/// Counters of what an has sent so far, matching the reference client's +/// stats. All members are safe to read while requests are in flight. +/// +public sealed class Statistics +{ + private readonly object _lock = new(); + private readonly List _rateLimitErrors = new(); + private long _calls; + private long _requests; + + /// The number of client calls made (each call may send several requests when it is retried). + public long Calls => Interlocked.Read(ref _calls); + + /// The number of HTTP requests sent, counting every retry attempt. + public long Requests => Interlocked.Read(ref _requests); + + /// + /// The number of times the API answered 429 (rate limit exceeded). Errors on the first attempt are + /// counted at index 0, errors on the first retry at index 1, and so on. A snapshot copy. + /// + public IReadOnlyList RateLimitErrors + { + get + { + lock (_lock) + { + return _rateLimitErrors.ToArray(); + } + } + } + + internal void AddCall() => Interlocked.Increment(ref _calls); + + internal void AddRequest() => Interlocked.Increment(ref _requests); + + internal void AddRateLimitError(int attempt) + { + var index = attempt - 1; + lock (_lock) + { + while (_rateLimitErrors.Count <= index) + { + _rateLimitErrors.Add(0); + } + + _rateLimitErrors[index]++; + } + } +} diff --git a/src/Apify.Client/StreamedLog.cs b/src/Apify.Client/StreamedLog.cs index 45c8a2b..f7d3991 100644 --- a/src/Apify.Client/StreamedLog.cs +++ b/src/Apify.Client/StreamedLog.cs @@ -59,6 +59,32 @@ internal StreamedLog(LogClient logClient, Action toLog, bool fromStart) _relevancyTimeLimit = fromStart ? null : DateTimeOffset.UtcNow; } + /// + /// Pass this as the log argument of ActorClient.CallAsync or TaskClient.CallAsync to get + /// the reference client's default redirection: once the run has started, its log is written to the + /// standard output with the prefix <Actor name> runId:<id> -> (the run and its Actor are + /// fetched to build it, as the reference client does). Only CallAsync recognizes it: + /// uses a sink as it is, so there it writes the messages without the + /// prefix; use for a known run. + /// + public static readonly Action DefaultConsole = static message => Console.Out.WriteLine(message); + + /// + /// Creates a ready-made sink that writes every message with the reference client's default prefix, + /// <Actor name> runId:<id> -> , one message per line, for a run that is already known (for + /// ). CallAsync creates the run itself, so pass + /// there instead. + /// + /// The run ID shown in the prefix. + /// The Actor name shown in the prefix; omitted from it when null or empty. + /// Where the lines are written (default: the standard output). + public static Action CreatePrefixedSink(string runId, string? actorName = null, TextWriter? writer = null) + { + var name = string.IsNullOrEmpty(actorName) ? "runId:" + runId : actorName + " runId:" + runId; + var prefix = name + " -> "; + return message => (writer ?? Console.Out).WriteLine(prefix + message); + } + /// Starts redirecting the log on a background task. Throws if already started. public void Start() { diff --git a/tests/Apify.Client.Tests/Apify.Client.Tests.csproj b/tests/Apify.Client.Tests/Apify.Client.Tests.csproj index 9f2b502..21ab82f 100644 --- a/tests/Apify.Client.Tests/Apify.Client.Tests.csproj +++ b/tests/Apify.Client.Tests/Apify.Client.Tests.csproj @@ -3,9 +3,8 @@ false true - + false - false diff --git a/tests/Apify.Client.Tests/Integration/ActorRunIntegrationTests.cs b/tests/Apify.Client.Tests/Integration/ActorRunIntegrationTests.cs index 8c08d68..45d9d26 100644 --- a/tests/Apify.Client.Tests/Integration/ActorRunIntegrationTests.cs +++ b/tests/Apify.Client.Tests/Integration/ActorRunIntegrationTests.cs @@ -9,6 +9,21 @@ namespace Apify.Client.Tests.Integration; [Trait("Category", "Integration")] public sealed class ActorRunIntegrationTests : IntegrationTestBase { + [SkippableFact] + public async Task ListRunsByDeployment() + { + var client = RequireClient(); + var page = await client.Runs().ListAsync( + new ListOptions { Limit = 5 }, + new RunListOptions { Status = new[] { "READY", "RUNNING" }, Deployment = new[] { "SINGLE_TENANT" } }); + + Assert.All(page.Items, static run => + { + Assert.Contains(run.Status, new[] { "READY", "RUNNING" }); + Assert.Equal("SINGLE_TENANT", run.Standby?["deployment"]?.GetValue()); + }); + } + [SkippableFact] public async Task ListRuns() { diff --git a/tests/Apify.Client.Tests/Integration/IntegrationTestBase.cs b/tests/Apify.Client.Tests/Integration/IntegrationTestBase.cs index 46efb42..27e24e7 100644 --- a/tests/Apify.Client.Tests/Integration/IntegrationTestBase.cs +++ b/tests/Apify.Client.Tests/Integration/IntegrationTestBase.cs @@ -26,14 +26,16 @@ public abstract class IntegrationTestBase /// Retry budget for : how many times a freshly created /// resource's collection listing is re-scanned before giving up on it having propagated. /// - protected const int EventualConsistencyAttempts = 16; + protected const int EventualConsistencyAttempts = 31; /// /// Delay between retries in . Combined with /// , the total wait budget is - /// (EventualConsistencyAttempts - 1) * EventualConsistencyBackoff = ~15s. + /// (EventualConsistencyAttempts - 1) * EventualConsistencyBackoff = ~90s. The listing endpoints read + /// from database secondaries that may lag by up to 90 seconds, so a shorter budget makes the listing + /// checks fail now and then on a resource the test has just created. /// - protected static readonly TimeSpan EventualConsistencyBackoff = TimeSpan.FromSeconds(1); + protected static readonly TimeSpan EventualConsistencyBackoff = TimeSpan.FromSeconds(3); /// /// Derives the client base URL from an optional APIFY_API_URL. The variable includes the diff --git a/tests/Apify.Client.Tests/Integration/StoreIntegrationTests.cs b/tests/Apify.Client.Tests/Integration/StoreIntegrationTests.cs index 655c9b9..691439f 100644 --- a/tests/Apify.Client.Tests/Integration/StoreIntegrationTests.cs +++ b/tests/Apify.Client.Tests/Integration/StoreIntegrationTests.cs @@ -45,4 +45,14 @@ public async Task IterateStoreStopsAtLimit() Assert.Equal(7, count); } + + [SkippableFact] + public async Task ListStoreWithInputSchema() + { + var client = RequireClient(); + var page = await client.Store().ListAsync(new StoreListOptions { IncludeInputSchema = true, Limit = StoreListOptions.MaxPageLimitWithInputSchema }); + + Assert.NotEmpty(page.Items); + Assert.Contains(page.Items, static item => item.Get("inputSchema") is not null); + } } diff --git a/tests/Apify.Client.Tests/Integration/UserIntegrationTests.cs b/tests/Apify.Client.Tests/Integration/UserIntegrationTests.cs index 09182c1..6691038 100644 --- a/tests/Apify.Client.Tests/Integration/UserIntegrationTests.cs +++ b/tests/Apify.Client.Tests/Integration/UserIntegrationTests.cs @@ -55,4 +55,15 @@ public async Task GetLimits() var client = RequireClient(); Assert.NotEmpty(await client.Me().LimitsAsync()); } + + [SkippableFact] + public async Task GetLimitsAndMonthlyUsageByUserId() + { + var client = RequireClient(); + var me = await client.Me().GetAsync(); + Assert.NotNull(me); + + Assert.NotEmpty(await client.User(me!.Id!).LimitsAsync()); + Assert.NotEmpty(await client.User(me.Id!).MonthlyUsageAsync()); + } } diff --git a/tests/Apify.Client.Tests/Unit/BatchAddRequestsTests.cs b/tests/Apify.Client.Tests/Unit/BatchAddRequestsTests.cs index 6cf5d8d..fae63d8 100644 --- a/tests/Apify.Client.Tests/Unit/BatchAddRequestsTests.cs +++ b/tests/Apify.Client.Tests/Unit/BatchAddRequestsTests.cs @@ -100,6 +100,20 @@ public async Task MultiChunkPreservesEarlierChunksWhenLaterChunkFails() Assert.Equal(5, result.UnprocessedRequests.Count); } + [Fact] + public async Task UnprocessedRequestsCountAsARateLimitErrorInStats() + { + var transport = new MockTransport() + .QueueResponse(200, BatchResponse(new[] { "r0" }, new[] { "r1" })) + .QueueResponse(200, BatchResponse(new[] { "r1" })); + var client = Client(transport); + var requests = new List { new("https://x/r0", "r0"), new("https://x/r1", "r1") }; + + await client.RequestQueue("q1").BatchAddRequestsAsync(requests, false, FastOptions()); + + Assert.Equal(new long[] { 1 }, client.Stats.RateLimitErrors); + } + [Fact] public async Task RetriesOnlyUnprocessedFromSuccessfulResponse() { @@ -271,4 +285,58 @@ public async Task OversizedSingleRequestThrows() await Assert.ThrowsAsync(() => Client(new MockTransport()).RequestQueue("q1").BatchAddRequestsAsync(requests)); } + + [Fact] + public async Task BatchBodyIsTheSerializedRequestsJoinedIntoACompactArray() + { + var transport = new MockTransport().QueueResponse(200, BatchResponse(new[] { "k0", "k1" })); + var requests = new List { new("https://x/0", "k0"), new("https://x/1", "k1") }; + + await Client(transport).RequestQueue("q1").BatchAddRequestsAsync(requests, false, FastOptions()); + + Assert.Equal( + "[" + requests[0].ToJsonObject().ToJsonString() + "," + requests[1].ToJsonObject().ToJsonString() + "]", + transport.Received[0].Body); + } + + [Fact] + public async Task OversizedRequestIsRejectedBeforeAnyBatchIsSent() + { + var transport = new MockTransport().QueueResponse(200, BatchResponse(new[] { "small" })); + var huge = new string('x', 10 * 1024 * 1024); + var requests = new List + { + new("https://x/small", "small"), + new("https://x/big", "big") { UserData = new JsonObject { ["blob"] = huge } }, + }; + + var error = await Assert.ThrowsAsync( + () => Client(transport).RequestQueue("q1").BatchAddRequestsAsync(requests, false, FastOptions())); + + Assert.Contains("index 1", error.Message, StringComparison.Ordinal); + Assert.Equal(0, transport.CallCount); + } + + [Fact] + public async Task BatchBoundaryCountsTheBracketsAndCommas() + { + // The payload limit is 9 MiB minus the 0.01% safety margin the client subtracts (rounded up). + const int nineMiB = 9 * 1024 * 1024; + var limit = nineMiB - (int)Math.Ceiling(nineMiB * 0.0001); + var emptyBlobSize = new RequestQueueRequest("https://x/a", "a") { UserData = new JsonObject { ["blob"] = string.Empty } } + .ToJsonObject().ToJsonString().Length; + // Each request is just under half the limit, so the two bare objects fit together. Only the brackets + // and the comma of the array body push the pair over the limit, so the two requests must be split. + var blobLength = ((limit - 2) / 2) - emptyBlobSize; + var requests = new List + { + new("https://x/a", "a") { UserData = new JsonObject { ["blob"] = new string('x', blobLength) } }, + new("https://x/b", "b") { UserData = new JsonObject { ["blob"] = new string('x', blobLength) } }, + }; + var transport = new MockTransport().QueueResponse(200, BatchResponse(new[] { "a" })).QueueResponse(200, BatchResponse(new[] { "b" })); + + await Client(transport).RequestQueue("q1").BatchAddRequestsAsync(requests, false, FastOptions()); + + Assert.Equal(2, transport.CallCount); + } } diff --git a/tests/Apify.Client.Tests/Unit/ChunkSizeTests.cs b/tests/Apify.Client.Tests/Unit/ChunkSizeTests.cs index 13df3e3..3f84b5d 100644 --- a/tests/Apify.Client.Tests/Unit/ChunkSizeTests.cs +++ b/tests/Apify.Client.Tests/Unit/ChunkSizeTests.cs @@ -281,4 +281,24 @@ public async Task StoreIterationDoesNotStopAtAnEarlierPagesStaleTotal() Assert.Equal(3, count); } + + [Fact] + public async Task StoreIterationWithInputSchemaNeverRequestsMoreThanTheApiMaximum() + { + var transport = new MockTransport().QueueResponse(200, SpecFixtures.Page(1, SpecFixtures.StoreItem("a"))).QueueResponse(200, SpecFixtures.Page(1)); + + await DrainAsync(Client(transport).Store().IterateAsync(new StoreListOptions { IncludeInputSchema = true, ChunkSize = 50 })); + + Assert.All(transport.Received, static r => Assert.Contains("limit=10", r.Uri, StringComparison.Ordinal)); + } + + [Fact] + public async Task StoreIterationWithInputSchemaKeepsASmallerChunkSize() + { + var transport = new MockTransport().QueueResponse(200, SpecFixtures.Page(1, SpecFixtures.StoreItem("a"))).QueueResponse(200, SpecFixtures.Page(1)); + + await DrainAsync(Client(transport).Store().IterateAsync(new StoreListOptions { IncludeInputSchema = true, ChunkSize = 4 })); + + Assert.All(transport.Received, static r => Assert.Contains("limit=4", r.Uri, StringComparison.Ordinal)); + } } diff --git a/tests/Apify.Client.Tests/Unit/ClientOptionsAndStatsTests.cs b/tests/Apify.Client.Tests/Unit/ClientOptionsAndStatsTests.cs new file mode 100644 index 0000000..577d40e --- /dev/null +++ b/tests/Apify.Client.Tests/Unit/ClientOptionsAndStatsTests.cs @@ -0,0 +1,245 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Net; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Apify.Client.Exceptions; +using Apify.Client.Http; +using Xunit; + +namespace Apify.Client.Tests.Unit; + +/// +/// Tests that swap the process-wide run alone: xUnit runs a collection that disables +/// parallelization after all the parallel ones have finished, so no other test writes to the captured writer. +/// +[CollectionDefinition(Name, DisableParallelization = true)] +public sealed class ProcessConsoleTests +{ + /// The collection name. + public const string Name = "Console"; +} + +/// +/// Client-level behaviour that mirrors the reference client: request statistics, the separate long and +/// maximum timeouts, the public base URL default, the User-Agent override and non-retried dataset pushes. +/// +[Trait("Category", "Unit")] +[Collection(ProcessConsoleTests.Name)] +public sealed class ClientOptionsAndStatsTests +{ + private const string NotFoundBody = "{\"error\":{\"type\":\"record-not-found\",\"message\":\"missing\"}}"; + + private static ApifyClient Client(MockTransport transport, Action? configure = null) + { + var options = new ApifyClientOptions + { + Token = "t", + MinDelayBetweenRetriesMillis = 1, + TimeoutSecs = 100, + HttpTransport = transport, + }; + configure?.Invoke(options); + return new ApifyClient(options); + } + + [Fact] + public async Task PushItemsDoesNotRetryATimedOutRequest() + { + var transport = new MockTransport().QueueError(timeout: true).QueueResponse(201, string.Empty); + + var error = await Assert.ThrowsAsync( + () => Client(transport).Dataset("ds1").PushItemsAsync(new { a = 1 })); + + Assert.True(error.IsTimeout); + Assert.Equal(1, transport.CallCount); + } + + [Fact] + public async Task PushItemsStillRetriesANonTimeoutNetworkError() + { + var transport = new MockTransport().QueueError().QueueResponse(201, string.Empty); + + await Client(transport).Dataset("ds1").PushItemsAsync(new { a = 1 }); + + Assert.Equal(2, transport.CallCount); + } + + [Fact] + public async Task PublicBaseUrlDefaultsToThePublicApiWhateverTheBaseUrlIs() + { + var transport = new MockTransport().QueueResponse(404, NotFoundBody); + var client = Client(transport, o => o.BaseUrl = "http://internal.local:8080"); + + var url = await client.KeyValueStore("s1").GetRecordPublicUrlAsync("k"); + + Assert.StartsWith("https://api.apify.com/v2/key-value-stores/s1/records/k", url, StringComparison.Ordinal); + Assert.StartsWith("http://internal.local:8080/v2/", transport.LastRequest.Uri, StringComparison.Ordinal); + } + + [Fact] + public async Task ExplicitPublicBaseUrlIsUsed() + { + var transport = new MockTransport().QueueResponse(404, NotFoundBody); + var client = Client(transport, o => o.PublicBaseUrl = "https://public.example"); + + var url = await client.KeyValueStore("s1").GetRecordPublicUrlAsync("k"); + + Assert.StartsWith("https://public.example/v2/key-value-stores/s1/records/k", url, StringComparison.Ordinal); + } + + [Fact] + public async Task StatsCountCallsRequestsAndRateLimitErrorsPerAttempt() + { + var transport = new MockTransport().QueueResponse(429, "{\"error\":{\"type\":\"x\",\"message\":\"slow down\"}}").QueueResponse(200, SpecFixtures.Actor("x")); + var client = Client(transport); + + await client.Actor("x").GetAsync(); + + Assert.Equal(1, client.Stats.Calls); + Assert.Equal(2, client.Stats.Requests); + Assert.Equal(new long[] { 1 }, client.Stats.RateLimitErrors); + } + + [Fact] + public async Task StatsRateLimitErrorsAreIndexedByAttempt() + { + var transport = new MockTransport() + .QueueResponse(200, SpecFixtures.Actor("x")) + .QueueResponse(429, "{}").QueueResponse(429, "{}").QueueResponse(200, SpecFixtures.Actor("x")); + var client = Client(transport); + + await client.Actor("x").GetAsync(); + await client.Actor("x").GetAsync(); + + Assert.Equal(2, client.Stats.Calls); + Assert.Equal(4, client.Stats.Requests); + Assert.Equal(new long[] { 1, 1 }, client.Stats.RateLimitErrors); + } + + [Fact] + public async Task LongTierUsesTimeoutLongSecsWhileOverridesAreCappedAtTimeoutSecs() + { + var transport = new MockTransport() + .QueueResponse(404, NotFoundBody) + .QueueResponse(404, NotFoundBody); + var client = Client(transport, o => + { + o.TimeoutSecs = 100; + o.TimeoutLongSecs = 20; + }); + + // HEAD on a record is a Long-tier call. + await client.KeyValueStore("s1").RecordExistsAsync("k"); + await client.KeyValueStore("s1").WithTimeout(TimeSpan.FromSeconds(500)).RecordExistsAsync("k"); + + Assert.Equal(20.0, transport.Timeouts[0]); + Assert.Equal(100.0, transport.Timeouts[1]); + } + + [Fact] + public async Task LongTierDefaultsToTimeoutSecs() + { + var transport = new MockTransport().QueueResponse(404, NotFoundBody); + + await Client(transport).KeyValueStore("s1").RecordExistsAsync("k"); + + Assert.Equal(100.0, transport.Timeouts[0]); + } + + [Fact] + public async Task UserAgentPropertyReflectsAHeadersOverride() + { + var transport = new MockTransport().QueueResponse(200, SpecFixtures.Actor("x")); + var client = Client(transport, o => o.Headers = new Dictionary { ["user-agent"] = "custom-agent" }); + + await client.Actor("x").GetAsync(); + + Assert.Equal("custom-agent", client.UserAgent); + Assert.Equal(client.UserAgent, transport.LastRequest.Header("User-Agent")); + } + + [Fact] + public void UserAgentPropertyIsTheBuiltInValueByDefault() + { + var client = Client(new MockTransport()); + + Assert.Contains("ApifyClient/", client.UserAgent, StringComparison.OrdinalIgnoreCase); + } + + [Fact] + public void GzipCompressorRejectsAnUndefinedLevel() + { + Assert.Throws(() => new GzipHttpCompressor((CompressionLevel)42)); + } + + [Fact] + public void PrefixedSinkWritesTheReferencePrefix() + { + var writer = new StringWriter { NewLine = "\n" }; + + var sink = StreamedLog.CreatePrefixedSink("run1", "my-actor", writer); + sink("hello"); + StreamedLog.CreatePrefixedSink("run2", null, writer)("bye"); + + Assert.Equal("my-actor runId:run1 -> hello\nrunId:run2 -> bye\n", writer.ToString()); + } + + [Fact] + public async Task InvalidKeyAndRequestIdErrorsNameTheCallersParameter() + { + var transport = new MockTransport(); + var client = Client(transport); + + var key = await Assert.ThrowsAsync(() => client.KeyValueStore("s1").GetRecordAsync(string.Empty)); + var id = await Assert.ThrowsAsync(() => client.RequestQueue("q1").DeleteRequestAsync("..")); + + var log = Assert.Throws(() => client.Log("..")); + var envVar = Assert.Throws(() => client.Actor("a").Version("0.1").EnvVar("..")); + + Assert.Equal("key", key.ParamName); + Assert.Equal("id", id.ParamName); + Assert.Equal("buildOrRunId", log.ParamName); + Assert.Equal("name", envVar.ParamName); + Assert.Equal(0, transport.CallCount); + } + + /// Answers by URL so the concurrent log stream and the run polling cannot race on a queue. + private sealed class RoutingTransport : IHttpTransport + { + public Task SendAsync(HttpRequestMessage request, TimeSpan timeout, bool streaming, CancellationToken cancellationToken) + { + var url = request.RequestUri!.AbsolutePath; + var body = url switch + { + _ when url.EndsWith("/log", StringComparison.Ordinal) => "2024-01-02T03:04:05.678Z hello from the run\n", + _ when url.EndsWith("/runs", StringComparison.Ordinal) => SpecFixtures.Run("run1", "RUNNING"), + _ when url.Contains("/actor-runs/", StringComparison.Ordinal) => SpecFixtures.Run("run1"), + _ => SpecFixtures.Actor("a", "my-actor"), + }; + return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(body) }); + } + } + + [Fact] + public async Task CallAsyncWithTheDefaultConsoleSinkRedirectsTheLogWithTheReferencePrefix() + { + var client = new ApifyClient(new ApifyClientOptions { Token = "t", HttpTransport = new RoutingTransport() }); + var original = Console.Out; + var writer = new StringWriter { NewLine = "\n" }; + Console.SetOut(writer); + try + { + await client.Actor("a").CallAsync(log: StreamedLog.DefaultConsole); + } + finally + { + Console.SetOut(original); + } + + Assert.Equal("my-actor runId:run1 -> 2024-01-02T03:04:05.678Z hello from the run\n", writer.ToString()); + } +} diff --git a/tests/Apify.Client.Tests/Unit/DefaultHeadersAndCompressorTests.cs b/tests/Apify.Client.Tests/Unit/DefaultHeadersAndCompressorTests.cs new file mode 100644 index 0000000..25017cd --- /dev/null +++ b/tests/Apify.Client.Tests/Unit/DefaultHeadersAndCompressorTests.cs @@ -0,0 +1,208 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Apify.Client.Http; +using Xunit; + +namespace Apify.Client.Tests.Unit; + +/// +/// The client-wide default headers and the pluggable request body compressor, matching the reference +/// client's headers and compression options. +/// +[Trait("Category", "Unit")] +public sealed class DefaultHeadersAndCompressorTests +{ + private static readonly string LargeItem = "{\"blob\":\"" + new string('x', 4096) + "\"}"; + + private static ApifyClient Client(MockTransport transport, Action? configure = null) + { + var options = new ApifyClientOptions + { + Token = "t", + MinDelayBetweenRetriesMillis = 1, + TimeoutSecs = 5, + HttpTransport = transport, + }; + configure?.Invoke(options); + return new ApifyClient(options); + } + + private static async Task Decompress(byte[] data, Func wrap) + { + using var input = new MemoryStream(data); + await using var decompressor = wrap(input); + using var output = new MemoryStream(); + await decompressor.CopyToAsync(output); + return Encoding.UTF8.GetString(output.ToArray()); + } + + [Fact] + public async Task DefaultHeadersAreSentWithEveryRequest() + { + var transport = new MockTransport().QueueResponse(200, string.Empty).QueueResponse(200, string.Empty); + var client = Client(transport, o => o.Headers = new Dictionary { ["X-Integration"] = "my-platform" }); + + await client.Dataset("ds1").PushItemsAsync(new { a = 1 }); + await client.Dataset("ds1").PushItemsAsync(new { a = 2 }); + + Assert.Equal("my-platform", transport.Received[0].Header("X-Integration")); + Assert.Equal("my-platform", transport.Received[1].Header("X-Integration")); + } + + [Fact] + public async Task DefaultHeadersWinOverTheBuiltInDefaultsCaseInsensitively() + { + var transport = new MockTransport().QueueResponse(200, string.Empty); + var client = Client(transport, o => o.Headers = new Dictionary + { + ["user-agent"] = "custom-agent", + ["authorization"] = "Bearer other", + }); + + await client.Dataset("ds1").PushItemsAsync(new { a = 1 }); + + Assert.Equal("custom-agent", transport.LastRequest.Header("User-Agent")); + Assert.Equal("Bearer other", transport.LastRequest.Header("Authorization")); + } + + [Fact] + public async Task BuiltInDefaultsRemainWhenNoDefaultHeadersAreGiven() + { + var transport = new MockTransport().QueueResponse(200, string.Empty); + + await Client(transport).Dataset("ds1").PushItemsAsync(new { a = 1 }); + + Assert.Equal("Bearer t", transport.LastRequest.Header("Authorization")); + Assert.StartsWith("ApifyClient/", transport.LastRequest.Header("User-Agent"), StringComparison.Ordinal); + } + + [Fact] + public async Task ACustomCompressorIsUsedForLargeBodies() + { + var transport = new MockTransport().QueueResponse(200, string.Empty); + var client = Client(transport, o => o.Compressor = new MarkerCompressor()); + + await client.Dataset("ds1").PushItemsAsync(JsonNodeOf(LargeItem)); + + Assert.Equal("marker", transport.LastRequest.Header("Content-Encoding")); + Assert.Equal(Encoding.UTF8.GetBytes("MARK:" + LargeItem), transport.LastRequest.BodyBytes); + } + + [Fact] + public async Task ACustomCompressorIsNotUsedForSmallBodies() + { + var transport = new MockTransport().QueueResponse(200, string.Empty); + var client = Client(transport, o => o.Compressor = new MarkerCompressor()); + + await client.Dataset("ds1").PushItemsAsync(new { a = 1 }); + + Assert.Equal(string.Empty, transport.LastRequest.Header("Content-Encoding")); + } + + [Fact] + public async Task ACustomCompressorTakesPrecedenceOverTheRequestCompressionOption() + { + var transport = new MockTransport().QueueResponse(200, string.Empty); + var client = Client(transport, o => + { + o.RequestCompression = RequestCompression.Gzip; + o.Compressor = new BrotliHttpCompressor(quality: 1); + }); + + await client.Dataset("ds1").PushItemsAsync(JsonNodeOf(LargeItem)); + + Assert.Equal("br", transport.LastRequest.Header("Content-Encoding")); + } + + [Theory] + [InlineData(BrotliHttpCompressor.MinQuality)] + [InlineData(BrotliHttpCompressor.DefaultQuality)] + [InlineData(BrotliHttpCompressor.MaxQuality)] + public async Task BrotliCompressorRoundTripsAtEveryQuality(int quality) + { + var compressor = new BrotliHttpCompressor(quality); + + var compressed = await compressor.CompressAsync(Encoding.UTF8.GetBytes(LargeItem)); + + Assert.Equal("br", compressor.ContentEncoding); + Assert.Equal(LargeItem, await Decompress(compressed, s => new BrotliStream(s, CompressionMode.Decompress))); + } + + [Theory] + [InlineData(BrotliHttpCompressor.MinQuality - 1)] + [InlineData(BrotliHttpCompressor.MaxQuality + 1)] + public void BrotliCompressorRejectsAnOutOfRangeQuality(int quality) + { + Assert.Throws(() => new BrotliHttpCompressor(quality)); + } + + [Theory] + [InlineData(CompressionLevel.Fastest)] + [InlineData(CompressionLevel.Optimal)] + [InlineData(CompressionLevel.SmallestSize)] + public async Task GzipCompressorRoundTripsAtEveryLevel(CompressionLevel level) + { + var compressor = new GzipHttpCompressor(level); + + var compressed = await compressor.CompressAsync(Encoding.UTF8.GetBytes(LargeItem)); + + Assert.Equal("gzip", compressor.ContentEncoding); + Assert.Equal(LargeItem, await Decompress(compressed, s => new GZipStream(s, CompressionMode.Decompress))); + } + + private static System.Text.Json.Nodes.JsonNode JsonNodeOf(string json) => System.Text.Json.Nodes.JsonNode.Parse(json)!; + + /// A stand-in compressor that prefixes the body, so a test can recognize it. + private sealed class MarkerCompressor : HttpCompressor + { + public override string ContentEncoding => "marker"; + + public override Task CompressAsync(byte[] data, CancellationToken cancellationToken = default) + => Task.FromResult(Encoding.UTF8.GetBytes("MARK:" + Encoding.UTF8.GetString(data))); + } + + [Theory] + [InlineData("Content-Type")] + [InlineData("content-encoding")] + [InlineData("Content-Length")] + public void ContentHeadersAreRejectedAsDefaultHeaders(string name) + { + var transport = new MockTransport(); + + var error = Assert.Throws( + () => Client(transport, o => o.Headers = new Dictionary { [name] = "x" })); + + Assert.Contains(name, error.Message, StringComparison.Ordinal); + Assert.Contains("content header", error.Message, StringComparison.Ordinal); + } + + [Theory] + [InlineData("X Bad Name")] + [InlineData("X:Bad")] + public void MalformedHeaderNamesAreRejectedAsInvalidNotAsContentHeaders(string name) + { + var transport = new MockTransport(); + + var error = Assert.Throws( + () => Client(transport, o => o.Headers = new Dictionary { [name] = "x" })); + + Assert.Contains("not a valid HTTP header name", error.Message, StringComparison.Ordinal); + Assert.Contains("ApifyClientOptions.Headers", error.Message, StringComparison.Ordinal); + } + + [Fact] + public async Task GzipCompressorDefaultsToTheOptimalLevel() + { + var data = Encoding.UTF8.GetBytes(LargeItem); + + var byDefault = await new GzipHttpCompressor().CompressAsync(data); + var optimal = await new GzipHttpCompressor(CompressionLevel.Optimal).CompressAsync(data); + + Assert.Equal(optimal, byDefault); + } +} diff --git a/tests/Apify.Client.Tests/Unit/ModelAccessorTests.cs b/tests/Apify.Client.Tests/Unit/ModelAccessorTests.cs new file mode 100644 index 0000000..6fab50d --- /dev/null +++ b/tests/Apify.Client.Tests/Unit/ModelAccessorTests.cs @@ -0,0 +1,101 @@ +using System.Text.Json.Nodes; +using Apify.Client.Models; +using Xunit; + +namespace Apify.Client.Tests.Unit; + +/// +/// The typed accessors for fields the OpenAPI specification added to the build, run, storage, webhook, schedule +/// and request models (spec v2-2026-10-29T100357Z). +/// +[Trait("Category", "Unit")] +public sealed class ModelAccessorTests +{ + [Fact] + public void BuildExposesItsSourceAndExitFields() + { + var build = new Build(JsonNode.Parse( + "{\"changelog\":\"Fixed\",\"exitCode\":0,\"gitBranchName\":\"main\",\"gitCommitId\":\"abc123\"}")!.AsObject()); + + Assert.Equal("Fixed", build.Changelog); + Assert.Equal(0, build.ExitCode); + Assert.Equal("main", build.GitBranchName); + Assert.Equal("abc123", build.GitCommitId); + } + + [Fact] + public void RunExposesItsConsoleUrlNormalizedAndItsStandbyConfiguration() + { + var run = new ActorRun(JsonNode.Parse( + "{\"consoleUrl\":\"HTTPS://Console.Apify.com:443/view/runs/r1\",\"standby\":{\"deployment\":\"MULTI_TENANT_PAYING_USERS\"}}")!.AsObject()); + + Assert.Equal("https://console.apify.com/view/runs/r1", run.ConsoleUrl); + Assert.Equal("MULTI_TENANT_PAYING_USERS", run.Standby!["deployment"]!.GetValue()); + } + + [Fact] + public void RunWithoutStandbyHasNone() + { + var run = new ActorRun(new JsonObject()); + + Assert.Null(run.Standby); + Assert.Null(run.ConsoleUrl); + } + + [Fact] + public void StoragesExposeTheirTitle() + { + var data = JsonNode.Parse("{\"title\":\"My storage\"}")!.AsObject(); + + Assert.Equal("My storage", new Dataset(data).Title); + Assert.Equal("My storage", new KeyValueStore(data.DeepClone().AsObject()).Title); + Assert.Equal("My storage", new RequestQueue(data.DeepClone().AsObject()).Title); + } + + [Fact] + public void WebhookExposesItsTitleAndFlags() + { + var webhook = new Webhook(JsonNode.Parse("{\"title\":\"Notify\",\"isEnabled\":true,\"isApifyIntegration\":false}")!.AsObject()); + + Assert.Equal("Notify", webhook.Title); + Assert.True(webhook.IsEnabled); + Assert.False(webhook.IsApifyIntegration); + } + + [Fact] + public void ScheduleExposesItsTitleDescriptionAndEditorState() + { + var schedule = new Schedule(JsonNode.Parse("{\"title\":\"Nightly\",\"description\":\"d\",\"uiState\":{\"a\":1}}")!.AsObject()); + + Assert.Equal("Nightly", schedule.Title); + Assert.Equal("d", schedule.Description); + Assert.Equal(1, schedule.UiState!["a"]!.GetValue()); + } + + [Fact] + public void ScheduleWithNullEditorStateHasNone() + { + var schedule = new Schedule(JsonNode.Parse("{\"uiState\":null}")!.AsObject()); + + Assert.Null(schedule.UiState); + } + + [Fact] + public void RequestExposesItsLockHolderAndHandledAt() + { + var request = RequestQueueRequest.FromJsonObject(JsonNode.Parse( + "{\"uniqueKey\":\"k\",\"url\":\"https://a.com\",\"lockByClient\":\"client-1\",\"handledAt\":\"2026-01-01T00:00:00.000Z\"}")!.AsObject()); + + Assert.Equal("client-1", request.LockByClient); + Assert.Equal("2026-01-01T00:00:00.000Z", request.HandledAt); + } + + [Fact] + public void PendingRequestWithNullHandledAtHasNoHandledAt() + { + var request = RequestQueueRequest.FromJsonObject(JsonNode.Parse( + "{\"id\":\"r1\",\"uniqueKey\":\"k\",\"url\":\"https://a.com\",\"handledAt\":null}")!.AsObject()); + + Assert.Null(request.HandledAt); + } +} diff --git a/tests/Apify.Client.Tests/Unit/PathSegmentTests.cs b/tests/Apify.Client.Tests/Unit/PathSegmentTests.cs new file mode 100644 index 0000000..eb411ee --- /dev/null +++ b/tests/Apify.Client.Tests/Unit/PathSegmentTests.cs @@ -0,0 +1,102 @@ +using System; +using System.Threading.Tasks; +using Xunit; + +namespace Apify.Client.Tests.Unit; + +/// +/// Caller-supplied values interpolated into a URL path (resource ids, record keys, request IDs) must not be +/// able to restructure the request path, matching the reference client's URL path encoding. +/// +[Trait("Category", "Unit")] +public sealed class PathSegmentTests +{ + private static ApifyClient Client(MockTransport transport) => new(new ApifyClientOptions + { + Token = "t", + MinDelayBetweenRetriesMillis = 1, + TimeoutSecs = 5, + HttpTransport = transport, + }); + + [Fact] + public async Task RecordKeyIsEncodedInsteadOfRestructuringThePath() + { + var transport = new MockTransport().QueueResponse(200, "value").QueueResponse(200, "value").QueueResponse(200, "value"); + var store = Client(transport).KeyValueStore("STORE"); + + await store.GetRecordAsync("../../actor-runs/VICTIM/abort"); + Assert.Contains("/key-value-stores/STORE/records/..%2F..%2Factor-runs%2FVICTIM%2Fabort?", transport.LastRequest.Uri, StringComparison.Ordinal); + + await store.GetRecordAsync("foo?injected=1"); + Assert.Contains("/records/foo%3Finjected%3D1?", transport.LastRequest.Uri, StringComparison.Ordinal); + + await store.GetRecordAsync("foo#frag"); + Assert.Contains("/records/foo%23frag?", transport.LastRequest.Uri, StringComparison.Ordinal); + } + + [Fact] + public async Task RequestIdIsEncoded() + { + var transport = new MockTransport().QueueResponse(404, "{\"error\":{\"type\":\"record-not-found\",\"message\":\"m\"}}"); + + await Client(transport).RequestQueue("QUEUE").GetRequestAsync("../../../datasets/V/items"); + + Assert.EndsWith("/request-queues/QUEUE/requests/..%2F..%2F..%2Fdatasets%2FV%2Fitems", transport.LastRequest.Uri, StringComparison.Ordinal); + } + + [Fact] + public async Task EverySlashOfAResourceIdIsReplacedWithATilde() + { + var transport = new MockTransport().QueueResponse(404, "{\"error\":{\"type\":\"record-not-found\",\"message\":\"m\"}}"); + + await Client(transport).Actor("user/name/extra").GetAsync(); + + Assert.EndsWith("/actors/user~name~extra", transport.LastRequest.Uri, StringComparison.Ordinal); + } + + [Fact] + public async Task ResourceIdIsPercentEncoded() + { + var transport = new MockTransport().QueueResponse(404, "{\"error\":{\"type\":\"record-not-found\",\"message\":\"m\"}}"); + + await Client(transport).Dataset("ds?x=1#y").GetAsync(); + + Assert.EndsWith("/datasets/ds%3Fx%3D1%23y", transport.LastRequest.Uri, StringComparison.Ordinal); + } + + [Fact] + public async Task LiteralPathSeparatorsOfFixedSubPathsAreKept() + { + var transport = new MockTransport().QueueResponse(200, "{\"data\":{\"id\":\"abc\",\"lockExpiresAt\":\"2026-01-01T00:00:00.000Z\"}}"); + + await Client(transport).RequestQueue("QUEUE").ProlongRequestLockAsync("abc", 1); + + Assert.Contains("/request-queues/QUEUE/requests/abc/lock?", transport.LastRequest.Uri, StringComparison.Ordinal); + } + + [Theory] + [InlineData(".")] + [InlineData("..")] + public async Task DotSegmentsAreRejected(string segment) + { + var transport = new MockTransport(); + var client = Client(transport); + + await Assert.ThrowsAsync(() => client.KeyValueStore("STORE").GetRecordAsync(segment)); + await Assert.ThrowsAsync(() => client.RequestQueue("QUEUE").GetRequestAsync(segment)); + Assert.Throws(() => client.Dataset(segment)); + Assert.Equal(0, transport.CallCount); + } + + [Fact] + public async Task EmptyRecordKeyAndRequestIdAreRejected() + { + var transport = new MockTransport(); + var client = Client(transport); + + await Assert.ThrowsAsync(() => client.KeyValueStore("STORE").DeleteRecordAsync(string.Empty)); + await Assert.ThrowsAsync(() => client.RequestQueue("QUEUE").DeleteRequestAsync(string.Empty)); + Assert.Equal(0, transport.CallCount); + } +} diff --git a/tests/Apify.Client.Tests/Unit/PushItemsValidationTests.cs b/tests/Apify.Client.Tests/Unit/PushItemsValidationTests.cs new file mode 100644 index 0000000..99777cc --- /dev/null +++ b/tests/Apify.Client.Tests/Unit/PushItemsValidationTests.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.Text.Json.Nodes; +using System.Threading.Tasks; +using Xunit; + +namespace Apify.Client.Tests.Unit; + +/// +/// The dataset items endpoint only accepts a JSON object or an array of objects, so the client rejects any +/// other shape before sending a request, matching the reference client's narrowed pushItems(). +/// +[Trait("Category", "Unit")] +public sealed class PushItemsValidationTests +{ + private static ApifyClient Client(MockTransport transport) => new(new ApifyClientOptions + { + Token = "t", + MinDelayBetweenRetriesMillis = 1, + TimeoutSecs = 5, + HttpTransport = transport, + }); + + public static IEnumerable ValidItems() + { + yield return new object[] { new { a = 1 }, "{\"a\":1}" }; + yield return new object[] { new[] { new { a = 1 }, new { a = 2 } }, "[{\"a\":1},{\"a\":2}]" }; + yield return new object[] { new JsonObject { ["a"] = 1 }, "{\"a\":1}" }; + yield return new object[] { new List { new { a = 1 } }, "[{\"a\":1}]" }; + yield return new object[] { Array.Empty(), "[]" }; + } + + public static IEnumerable InvalidItems() + { + yield return new object[] { 5 }; + yield return new object[] { true }; + yield return new object[] { new[] { 1, 2 } }; + yield return new object[] { new object[] { new { a = 1 }, "text" } }; + yield return new object[] { new object[] { new[] { new { a = 1 } } } }; + } + + [Theory] + [MemberData(nameof(ValidItems))] + public async Task AnObjectOrAnArrayOfObjectsIsSent(object items, string expectedBody) + { + var transport = new MockTransport().QueueResponse(200, string.Empty); + + await Client(transport).Dataset("ds1").PushItemsAsync(items); + + Assert.Equal(expectedBody, transport.LastRequest.Body); + } + + [Theory] + [MemberData(nameof(InvalidItems))] + public async Task AnyOtherShapeIsRejectedBeforeARequestIsSent(object items) + { + var transport = new MockTransport(); + + await Assert.ThrowsAsync(() => Client(transport).Dataset("ds1").PushItemsAsync(items)); + + Assert.Equal(0, transport.CallCount); + } + + [Fact] + public async Task AStringIsSentAsAlreadySerializedJson() + { + var transport = new MockTransport().QueueResponse(200, string.Empty); + + await Client(transport).Dataset("ds1").PushItemsAsync("[{\"a\":1}]"); + + Assert.Equal("[{\"a\":1}]", transport.LastRequest.Body); + } +} diff --git a/tests/Apify.Client.Tests/Unit/RequestShapeTests.cs b/tests/Apify.Client.Tests/Unit/RequestShapeTests.cs index 2cdd36a..dd5df6e 100644 --- a/tests/Apify.Client.Tests/Unit/RequestShapeTests.cs +++ b/tests/Apify.Client.Tests/Unit/RequestShapeTests.cs @@ -761,4 +761,61 @@ private static byte[] Gzip(byte[] data) return output.ToArray(); } + + [Fact] + public async Task GetRequestAcceptsAPendingRequestWithANullHandledAt() + { + // A pending stored request carries only the required fields and handledAt: null; the response + // validation must accept it and the model must expose no handled time. + var transport = new MockTransport().QueueResponse(200, "{\"data\":{\"id\":\"r1\",\"uniqueKey\":\"k\",\"url\":\"https://a.com\",\"handledAt\":null}}"); + + var request = await Client(transport).RequestQueue("q1").GetRequestAsync("r1"); + + Assert.NotNull(request); + Assert.Equal("r1", request.Id); + Assert.Null(request.HandledAt); + } + + [Fact] + public async Task UserByIdServesLimitsAndMonthlyUsage() + { + var transport = new MockTransport() + .QueueResponse(200, SpecFixtures.AccountLimits()) + .QueueResponse(200, SpecFixtures.MonthlyUsage()) + .QueueResponse(200, string.Empty); + var user = Client(transport).User("abc"); + + await user.LimitsAsync(); + Assert.Equal("GET", transport.LastRequest.Method); + Assert.EndsWith("/users/abc/limits", transport.LastRequest.Uri, StringComparison.Ordinal); + + await user.MonthlyUsageAsync("2026-06-01"); + Assert.Contains("/users/abc/usage/monthly?date=2026-06-01", transport.LastRequest.Uri, StringComparison.Ordinal); + + await user.UpdateLimitsAsync(new { maxMonthlyUsageUsd = 5 }); + Assert.Equal("PUT", transport.LastRequest.Method); + Assert.EndsWith("/users/abc/limits", transport.LastRequest.Uri, StringComparison.Ordinal); + } + + [Fact] + public async Task RunListSendsTheDeploymentFilterAsACommaSeparatedList() + { + var transport = new MockTransport().QueueResponse(200, SpecFixtures.Page(0)); + + await Client(transport).Runs().ListAsync( + new ListOptions(), + new RunListOptions { Status = new[] { "READY", "RUNNING" }, Deployment = new[] { "SINGLE_TENANT", "MULTI_TENANT_OWNER_PAID" } }); + + Assert.Contains("deployment=SINGLE_TENANT%2CMULTI_TENANT_OWNER_PAID", transport.LastRequest.Uri, StringComparison.Ordinal); + } + + [Fact] + public async Task StoreListSendsIncludeInputSchema() + { + var transport = new MockTransport().QueueResponse(200, SpecFixtures.Page(0)); + + await Client(transport).Store().ListAsync(new StoreListOptions { IncludeInputSchema = true }); + + Assert.Contains("includeInputSchema=1", transport.LastRequest.Uri, StringComparison.Ordinal); + } } diff --git a/tests/Apify.Client.Tests/Unit/ResponseSchemaTests.cs b/tests/Apify.Client.Tests/Unit/ResponseSchemaTests.cs index ff39268..781a89a 100644 --- a/tests/Apify.Client.Tests/Unit/ResponseSchemaTests.cs +++ b/tests/Apify.Client.Tests/Unit/ResponseSchemaTests.cs @@ -65,6 +65,24 @@ public void NullIsAcceptedWhereTheSpecAllowsIt() Assert.Empty(Issues("GET", "/v2/actor-builds/b1", "{\"data\":{" + Build + ",\"actVersion\":{\"buildTag\":null}}}")); } + [Fact] + public void ScheduleUiStateMayBeNullAlthoughTheSpecTypesItAsAnObject() + { + // The API returns uiState as null once an update changed the cron expression or timezone and the + // Console editor state was dropped; the generator widens the spec for it (NULLABLE_OVERRIDES). + var schedule = SpecFixtures.ScheduleItem("s1").Replace("\"actions\":[]", "\"actions\":[],\"uiState\":null", StringComparison.Ordinal); + + Assert.Empty(Issues("GET", "/v2/schedules/s1", "{\"data\":" + schedule + "}")); + } + + [Fact] + public void ScheduleUiStateStillHasToBeAnObjectWhenPresent() + { + var schedule = SpecFixtures.ScheduleItem("s1").Replace("\"actions\":[]", "\"actions\":[],\"uiState\":5", StringComparison.Ordinal); + + Assert.Contains(Issues("GET", "/v2/schedules/s1", "{\"data\":" + schedule + "}"), issue => issue.StartsWith("$.data.uiState:", StringComparison.Ordinal)); + } + [Fact] public void NullIsRejectedWhereTheSpecDoesNotAllowIt() { diff --git a/tests/Apify.Client.Tests/Unit/SpecFixtures.cs b/tests/Apify.Client.Tests/Unit/SpecFixtures.cs index bea0c4c..cf56600 100644 --- a/tests/Apify.Client.Tests/Unit/SpecFixtures.cs +++ b/tests/Apify.Client.Tests/Unit/SpecFixtures.cs @@ -96,4 +96,18 @@ public static string QueueHead(params string[] items) /// A page of request queue requests (GET /v2/request-queues/{queueId}/requests) with the given raw request items. public static string ListRequestsPage(int limit, params string[] items) => "{\"data\":{\"items\":[" + string.Join(",", items) + "],\"limit\":" + limit.ToString(CultureInfo.InvariantCulture) + "}}"; + + /// The account and usage limits (GET /v2/users/{userId}/limits). + public static string AccountLimits() + => "{\"data\":{\"monthlyUsageCycle\":{\"startAt\":\"" + Timestamp + "\",\"endAt\":\"" + Timestamp + "\"}," + + "\"limits\":{\"maxMonthlyUsageUsd\":1,\"maxMonthlyActorComputeUnits\":1,\"maxMonthlyExternalDataTransferGbytes\":1,\"maxMonthlyProxySerps\":1," + + "\"maxMonthlyResidentialProxyGbytes\":1,\"maxActorMemoryGbytes\":1,\"maxActorCount\":1,\"maxActorTaskCount\":1,\"maxConcurrentActorJobs\":1," + + "\"maxTeamAccountSeatCount\":1,\"dataRetentionDays\":1}," + + "\"current\":{\"monthlyUsageUsd\":1,\"monthlyActorComputeUnits\":1,\"monthlyExternalDataTransferGbytes\":1,\"monthlyProxySerps\":1," + + "\"monthlyResidentialProxyGbytes\":1,\"actorMemoryGbytes\":1,\"actorCount\":1,\"actorTaskCount\":1,\"activeActorJobCount\":1,\"teamAccountSeatCount\":1}}}"; + + /// The monthly usage (GET /v2/users/{userId}/usage/monthly). + public static string MonthlyUsage() + => "{\"data\":{\"usageCycle\":{\"startAt\":\"" + Timestamp + "\",\"endAt\":\"" + Timestamp + "\"},\"monthlyServiceUsage\":{},\"dailyServiceUsages\":[]," + + "\"totalUsageCreditsUsdBeforeVolumeDiscount\":1,\"totalUsageCreditsUsdAfterVolumeDiscount\":1}}"; } diff --git a/tools/generate_response_schemas.py b/tools/generate_response_schemas.py index 9637346..56a53f1 100644 --- a/tools/generate_response_schemas.py +++ b/tools/generate_response_schemas.py @@ -27,6 +27,14 @@ SCHEMA_LIST_KEYWORDS = ("allOf", "anyOf", "oneOf") # Scalar constraints copied as they are. CONSTRAINT_KEYWORDS = ("format", "pattern", "minimum", "maximum", "minLength", "maxLength") +# Properties the API returns as `null` although the specification declares them non-nullable. The generator +# widens each of them to accept `null`, and fails once the specification itself allows `null` there, so a +# stale entry is removed instead of lingering. Mirrors the reference client's `ScheduleSpecNarrowings`. +NULLABLE_OVERRIDES = { + # `uiState` is `null` once an API update has changed the schedule's `cronExpression` or `timezone` and the + # Console editor state was dropped. + "Schedule": ("uiState",), +} def load_spec(source): @@ -61,6 +69,41 @@ def slim(schema): return result +def find_property(schema, name): + """Returns the (properties dict) declaring `name` in a schema or in its allOf/anyOf/oneOf parts, or None.""" + if not isinstance(schema, dict): + return None + if name in schema.get("properties", {}): + return schema["properties"] + for keyword in SCHEMA_LIST_KEYWORDS: + for part in schema.get(keyword, []): + found = find_property(part, name) + if found is not None: + return found + return None + + +def allows_null(schema): + if not isinstance(schema, dict): + return False + declared_type = schema.get("type") + if declared_type == "null" or (isinstance(declared_type, list) and "null" in declared_type): + return True + return any(allows_null(part) for keyword in ("anyOf", "oneOf") for part in schema.get(keyword, [])) + + +def apply_nullable_overrides(schemas): + for schema_name, property_names in NULLABLE_OVERRIDES.items(): + for property_name in property_names: + properties = find_property(schemas.get(schema_name), property_name) + if properties is None: + raise SystemExit("nullable override targets a missing property: %s.%s" % (schema_name, property_name)) + if allows_null(properties[property_name]): + raise SystemExit( + "stale nullable override: the specification already allows null in %s.%s" % (schema_name, property_name)) + properties[property_name] = {"anyOf": [properties[property_name], {"type": "null"}]} + + def collect_refs(node, found): if isinstance(node, dict): ref = node.get("$ref") @@ -107,6 +150,7 @@ def main(): schemas[name] = slim(spec["components"]["schemas"][name]) collect_refs(schemas[name], pending) + apply_nullable_overrides(schemas) document = {"specVersion": spec["info"]["version"], "operations": operations, "schemas": dict(sorted(schemas.items()))} OUTPUT.write_text(json.dumps(document, separators=(",", ":"), sort_keys=True) + "\n", encoding="utf-8") print("wrote", OUTPUT, "(%d operations, %d schemas)" % (len(operations), len(schemas)))