diff --git a/docs/04_upgrading/upgrading_to_v3.mdx b/docs/04_upgrading/upgrading_to_v3.mdx
index dcaaa0d72..ea1398070 100644
--- a/docs/04_upgrading/upgrading_to_v3.mdx
+++ b/docs/04_upgrading/upgrading_to_v3.mdx
@@ -391,13 +391,23 @@ Two options that carried a `@deprecated` marker throughout v2 have been removed.
`ActorClient.start()`, `call()`, `validateInput()` and `RunClient.metamorph()` took their `input` as `unknown`, so any value compiled, including ones the client cannot send.
-The input is now typed `ActorInput`, an alias for `object`, so it's an object or an array that the client serializes into the request body. Any other value stops compiling, including a value typed `unknown`, which has to be narrowed or cast first. To run an Actor without input, omit the argument or pass `undefined`.
+The input is now typed `ActorInput`, which accepts a plain object, an array of plain objects, or raw bytes as a `Uint8Array` (such as a `Buffer`) or an `ArrayBuffer`. The client serializes objects and arrays into the request body and sends bytes as they are. Any other value stops compiling, including a `Date`, `Map`, `Set` or function, and a value typed `unknown`, which has to be narrowed or cast first. To run an Actor without input, omit the argument or pass `undefined`.
```diff
- await client.actor('my-actor').call(null, { memory: 1024 }); // v2
+ await client.actor('my-actor').call(undefined, { memory: 1024 }); // v3
```
+An input declared as an `interface` or a class stops compiling as well, since `ActorInput` needs an index signature and neither gets one implicitly. Declare the input type as a `type` alias, or spread the value into a new object:
+
+```diff
+- interface MyInput { url: string } // v2
++ type MyInput = { url: string }; // v3
+
+- await client.actor('my-actor').call(input); // v2, with an interface-typed `input`
++ await client.actor('my-actor').call({ ...input }); // v3
+```
+
A raw `string` body stops compiling too, with or without a `contentType`. Pass the input as an object and let the client serialize it:
```diff
diff --git a/docs/public-api/apify-client.api.md b/docs/public-api/apify-client.api.md
index bf4c82a5f..32ffbaf7b 100644
--- a/docs/public-api/apify-client.api.md
+++ b/docs/public-api/apify-client.api.md
@@ -201,7 +201,7 @@ export interface ActorExampleRunInput extends GeneratedExampleRunInput {
}
// @public
-export type ActorInput = object;
+export type ActorInput = Dictionary | readonly Dictionary[] | Uint8Array | ArrayBuffer;
// @public
export interface ActorLastRunOptions {
diff --git a/src/resource_clients/actor.ts b/src/resource_clients/actor.ts
index e67f7877b..f35129047 100644
--- a/src/resource_clients/actor.ts
+++ b/src/resource_clients/actor.ts
@@ -8,6 +8,7 @@ import { ResourceClient } from '../base/resource_client.js';
import type { ApifyRequestConfig } from '../http_clients/index.js';
import type { Actor, ActorRun } from '../models.js';
import type { TimeoutOptions } from '../timeouts.js';
+import type { Dictionary } from '../utils.js';
import * as schemas from '../schemas.js';
import { timeoutOptionsSchema, timeoutOptionsShape } from '../timeouts.js';
import { anyObjectSchema, parseArgument, parseResponse, stringifyWebhooksToBase64 } from '../utils.js';
@@ -179,7 +180,8 @@ export class ActorClient extends ResourceClient {
* asynchronously and this method returns immediately without waiting for completion.
* Use the {@link call} method if you want to wait for the Actor to finish.
*
- * @param input - Input for the Actor, serialized to JSON. Omit it to run the Actor without input.
+ * @param input - Input for the Actor: an object or array serialized to JSON, or raw bytes sent as they are.
+ * Omit it to run the Actor without input.
* @param options - Run configuration options
* @param options.build - Tag or number of the build to run (e.g., `'beta'` or `'1.2.345'`). If not provided, uses the default build.
* @param options.memory - Memory in megabytes allocated for the run. If not provided, uses the Actor's default memory setting.
@@ -262,7 +264,8 @@ export class ActorClient extends ResourceClient {
* by polling the run status. It optionally streams logs to the console or a custom Log instance.
* By default, it waits indefinitely unless the `waitSecs` option is provided.
*
- * @param input - Input for the Actor, serialized to JSON. Omit it to run the Actor without input.
+ * @param input - Input for the Actor: an object or array serialized to JSON, or raw bytes sent as they are.
+ * Omit it to run the Actor without input.
* @param options - Run configuration options (extends all options from {@link start})
* @param options.waitSecs - Maximum time to wait for the run to finish, in seconds. If omitted, waits indefinitely.
* @param options.log - Log instance for streaming run logs. Use `'default'` for console output, `null` to disable logging, or provide a custom Log instance.
@@ -321,7 +324,8 @@ export class ActorClient extends ResourceClient {
* invalid, the API responds with an error that is thrown as an `ApifyApiError` describing the
* validation problem.
*
- * @param input - Input to validate against the Actor's input schema, serialized to JSON.
+ * @param input - Input to validate against the Actor's input schema: an object or array serialized to JSON, or
+ * raw bytes sent as they are.
* @param options - Validation options
* @param options.build - Tag or number of the build whose input schema the input is validated against
* (e.g., `'latest'` or `'1.2.345'`). If not provided, uses the default build.
@@ -588,10 +592,12 @@ export type ActorUpdateOptions = Partial<
* Input for an Actor run, as taken by {@link ActorClient.start}, {@link ActorClient.call},
* {@link ActorClient.validateInput} and {@link RunClient.metamorph}.
*
- * An object or an array. Declared as `object` rather than an index-signature type such as
- * `Dictionary`, which would reject a caller's own `interface`.
+ * A plain object or an array of plain objects, serialized into the request body, or raw bytes
+ * (a `Uint8Array` such as a `Buffer`, or an `ArrayBuffer`) sent as they are, typically with a
+ * `contentType`. An input typed as an `interface` or a class needs a spread (`{ ...input }`), or for
+ * an interface a `type` alias, to compile.
*/
-export type ActorInput = object;
+export type ActorInput = Dictionary | readonly Dictionary[] | Uint8Array | ArrayBuffer;
export interface ActorStartOptions extends TimeoutOptions {
/**
diff --git a/src/resource_clients/run.ts b/src/resource_clients/run.ts
index 349d70374..2413eb1d3 100644
--- a/src/resource_clients/run.ts
+++ b/src/resource_clients/run.ts
@@ -167,7 +167,8 @@ export class RunClient extends ResourceClient {
* This is useful for chaining Actor executions or implementing complex workflows.
*
* @param targetActorId - ID or username/name of the target Actor
- * @param input - Input for the target Actor, serialized to JSON. Omit it to metamorph without input.
+ * @param input - Input for the target Actor: an object or array serialized to JSON, or raw bytes sent as they
+ * are. Omit it to metamorph without input.
* @param options - Metamorph options
* @param options.build - Tag or number of the target Actor's build to run. Default is the target Actor's default build.
* @param options.timeoutSecs - Timeout for the API request. Default is `'medium'`.
diff --git a/test/actors.test.ts b/test/actors.test.ts
index 7b251808a..107603362 100644
--- a/test/actors.test.ts
+++ b/test/actors.test.ts
@@ -3,7 +3,7 @@ import type { AddressInfo } from 'node:net';
import { setTimeout } from 'node:timers/promises';
import c from 'ansi-colors';
-import type { ActorCollectionCreateOptions, ActorCollectionListOptions, ActorVersion } from 'apify-client';
+import type { ActorCollectionCreateOptions, ActorCollectionListOptions, ActorInput, ActorVersion } from 'apify-client';
import {
ActorListSortBy,
ActorSourceType,
@@ -14,7 +14,7 @@ import {
} from 'apify-client';
import express from 'express';
import type { Page } from 'puppeteer';
-import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest';
+import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, expectTypeOf, test, vi } from 'vitest';
import { META_ORIGINS, WEBHOOK_EVENT_TYPES } from '@apify/consts';
import { LEVELS, Log } from '@apify/log';
@@ -239,6 +239,29 @@ describe('Actor methods', () => {
});
});
+ test('ActorInput accepts plain objects, arrays of them and raw bytes, but no other objects', () => {
+ interface InterfaceInput {
+ url: string;
+ }
+ type AliasInput = { url: string };
+
+ expectTypeOf<{ url: string }>().toExtend();
+ expectTypeOf().toExtend();
+ expectTypeOf().toExtend();
+ expectTypeOf().toExtend();
+ expectTypeOf().toExtend();
+ expectTypeOf().toExtend();
+ expectTypeOf().toExtend();
+
+ expectTypeOf().not.toExtend();
+ expectTypeOf().not.toExtend();
+ expectTypeOf