Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
d912c73
feat: add `standby_request` for calling Standby Actors
vdusek Oct 19, 2026
7b85f25
chore: keep error class names intact when syncing async docstrings
vdusek Oct 19, 2026
f7f6d0a
docs: document param conversion and ValueError in `standby_request`
vdusek Oct 19, 2026
181ac08
test: cover more `standby_request` body and header cases
vdusek Oct 19, 2026
3f3d397
docs: shorten the Standby Actors concept page
vdusek Oct 19, 2026
8fc1b16
docs: link the platform docs for Standby authentication
vdusek Oct 19, 2026
4b61d87
feat: add per-request `compression` argument to HTTP clients
vdusek Oct 20, 2026
daabf01
feat: add opt-in body compression to `standby_request`
vdusek Oct 20, 2026
b3995a6
docs: document Standby authentication and body compression
vdusek Oct 20, 2026
2136cca
Merge remote-tracking branch 'origin/master' into feat/standby-request
vdusek Oct 23, 2026
5f04b11
refactor: rename `standby_request` to `server_request`
vdusek Oct 23, 2026
0a42f08
Merge remote-tracking branch 'origin/master' into feat/standby-request
vdusek Oct 26, 2026
4b33343
docs: say Server Actor request bodies are uncompressed by default
vdusek Oct 26, 2026
e22661c
refactor: narrow server_request method and params types
vdusek Oct 26, 2026
5fe9c3c
feat: add get_server_url to Actor clients
vdusek Oct 26, 2026
6f049b8
Merge remote-tracking branch 'origin/master' into feat/standby-request
vdusek Oct 26, 2026
f986ba2
feat: add get_server_openapi_spec to Actor clients
vdusek Oct 26, 2026
4369f0c
Merge remote-tracking branch 'origin/master' into feat/standby-request
vdusek Oct 26, 2026
9514c19
docs: document get_server_url and get_server_openapi_spec
vdusek Oct 26, 2026
1524fba
docs: note that a replaced HttpClient.call must accept compression
vdusek Oct 26, 2026
4eaa7f0
style: keep the Content-Encoding comment next to its check
vdusek Oct 26, 2026
0addab8
docs: clarify the build-number lookup comment in get_server_openapi_spec
vdusek Oct 26, 2026
2b9c478
feat: add deprecated Standby-named aliases of the Server Actor methods
vdusek Oct 28, 2026
a39c78f
docs: use "server interface" terminology for Server Actors, matching …
vdusek Oct 28, 2026
9d40e33
refactor: say in the Standby alias deprecations that they will be rem…
vdusek Oct 28, 2026
11fea02
refactor: move the Server Actor methods to an ActorServerClient sub-c…
vdusek Oct 29, 2026
b2db979
docs: present Actor Standby as the former name of Server Actors and d…
vdusek Oct 29, 2026
4293974
docs: tweak the Server Actors page wording
vdusek Oct 29, 2026
b72a07c
test: add an integration test for the Actor server client
vdusek Oct 29, 2026
08a3dcd
Merge remote-tracking branch 'origin/master' into feat/standby-request
vdusek Oct 29, 2026
56c78ba
refactor: type Actor server request params with `QueryParams`
vdusek Oct 29, 2026
13a444c
feat: add authorization header, error status and retry status options…
vdusek Oct 29, 2026
567fc31
feat: send the token to Actor servers in the X-Apify-Authorization he…
vdusek Oct 29, 2026
85da171
feat: return Actor server error responses instead of raising
vdusek Oct 29, 2026
d7f7376
feat: add a stream option to ActorServerClient.request()
vdusek Oct 29, 2026
d612058
feat: add a timeout option to ActorServerClient.get_url() and get_ope…
vdusek Oct 29, 2026
f79dc9e
perf: reuse the server URL fetched by get_openapi_spec()
vdusek Oct 29, 2026
23928aa
docs: drop the cold-start duration from the Server Actors page
vdusek Oct 29, 2026
b90d6f8
test: cover Actor server error responses in the integration test
vdusek Oct 29, 2026
95e94fa
feat: stop retrying Actor server requests after transport errors
vdusek Oct 30, 2026
0581db0
docs: list every error ActorServerClient.request can raise
vdusek Oct 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ For a guided walkthrough — authenticating, running an Actor, and reading its r
- **Convenience methods** — `call()`, `wait_for_finish()`, nested resource access, and other shortcuts that hide platform quirks ([Convenience methods](https://docs.apify.com/api/client/python/docs/concepts/convenience-methods)).
- **Pluggable HTTP layer** — use the default [Impit](https://github.com/apify/impit)-based client, opt in to the built-in [HTTPX2](https://github.com/pydantic/httpx2) client, or plug in any custom implementation ([HTTP clients](https://docs.apify.com/api/client/python/docs/concepts/custom-http-clients)).
- **Structured errors** — every API error surfaces as an [`ApifyApiError`](https://docs.apify.com/api/client/python/reference/class/ApifyApiError) with HTTP-specific subclasses for precise handling ([Error handling](https://docs.apify.com/api/client/python/docs/concepts/error-handling)).
- **Server Actors** — send HTTP requests to an Actor's server interface (formerly Actor Standby), and get the server's URL and OpenAPI specification ([Server Actors](https://docs.apify.com/api/client/python/docs/concepts/server-actors)).
- **Debug logging** — opt-in structured logging on the `apify_client` logger captures request URLs, status codes, retry attempts, and more ([Logging](https://docs.apify.com/api/client/python/docs/concepts/logging)).

## Usage examples
Expand Down
2 changes: 1 addition & 1 deletion docs/02_concepts/10_custom_http_clients.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ Use the <ApiLink to="class/ApifyClient#with_custom_http_client">`ApifyClient.wit
After that, all API calls made through the client will go through your custom HTTP client.

:::warning
If you override `call` itself, your implementation becomes responsible for request preparation, retries, timeouts, API error conversion, logging, and statistics. Implementing the transport hooks and inheriting `call` keeps the shared behavior.
If you override `call` itself, your implementation becomes responsible for request preparation, retries, timeouts, API error conversion, logging, and statistics. It also has to honor the `compression`, `authorization_header`, `raise_for_status`, `retry_status_codes`, and `retry_transport_errors` arguments. <ApiLink to="class/ActorServerClient#request">`ActorServerClient.request`</ApiLink> relies on them to keep the API token away from the Actor's code, to return the server's error responses, and to avoid sending a request to the Actor twice. Implementing the transport hooks and inheriting `call` keeps the shared behavior.
:::

If you override `call` and prepare requests with the inherited helpers, a streamed body arrives as a <ApiLink to="class/StreamedRequestBody">`StreamedRequestBody`</ApiLink>. Send its `iter_bytes()` or `aiter_bytes()`, and send the body a second time only when `rewindable` is true, after calling `rewind()`. For what the client streams and how, see [Streaming uploads](./09_streaming.mdx#streaming-uploads).
Expand Down
87 changes: 87 additions & 0 deletions docs/02_concepts/14_server_actors.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
---
id: server-actors
title: Server Actors
description: Send HTTP requests to Server Actors (formerly Actor Standby), and get their URL and OpenAPI specification.
keywords: [server actor, server interface, standby, actor standby, standby_url, ActorServerClient]
---

import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import CodeBlock from '@theme/CodeBlock';
import ApiLink from '@theme/ApiLink';

import ServerAsyncExample from '!!raw-loader!./code/14_server_async.py';
import ServerSyncExample from '!!raw-loader!./code/14_server_sync.py';
import ServerUrlSpecAsyncExample from '!!raw-loader!./code/14_server_url_spec_async.py';
import ServerUrlSpecSyncExample from '!!raw-loader!./code/14_server_url_spec_sync.py';

A [Server Actor](https://docs.apify.com/platform/actors/running/standby) (formerly Actor Standby) runs a web server. You call it over HTTP, and the platform keeps a run ready to answer, so each call skips the run startup.

:::note Formerly Actor Standby

Actor Standby is the former name of Server Actors, and it'll be deprecated. The platform documentation and the API still use it, for example in the Actor's `standby_url` and `actor_standby` fields, and in the `actor_standby_*` arguments of `ActorClient.update`. In the client, `ActorClient.standby` is a deprecated alias of `ActorClient.server` that will be removed in v4.

:::

To work with a Server Actor, get its server client with <ApiLink to="class/ActorClient#server">`ActorClient.server`</ApiLink>. To send a request, use <ApiLink to="class/ActorServerClient#request">`ActorServerClient.request`</ApiLink>. It takes the path on the Actor's server, and optionally the HTTP method, query parameters, headers, and a body:

<Tabs>
<TabItem value="AsyncExample" label="Async client" default>
<CodeBlock className="language-python">
{ServerAsyncExample}
</CodeBlock>
</TabItem>
<TabItem value="SyncExample" label="Sync client">
<CodeBlock className="language-python">
{ServerSyncExample}
</CodeBlock>
</TabItem>
</Tabs>

The method returns the server's response. To read a large body in chunks, pass `stream=True` and close the response when you're done with it. To find out which endpoints an Actor serves, see its page in [Apify Store](https://apify.com/store), its README, or its OpenAPI specification. For details, see [Server URL and OpenAPI specification](#server-url-and-openapi-specification).

## How the request is sent

Note that:

- The client takes the server's URL from the Actor's `standby_url`. It fetches the Actor from the API on the first call and reuses the URL for later calls through the same `ActorClient`. If the Actor isn't a Server Actor, the server client raises an `ApifyClientError`.
- The path is always resolved against that URL, so a path can't send the request to another host.
- By default, the request body is sent uncompressed, since the Actor's server may not decode a compressed body. If the server decodes it, pass `compression`, for example `compression='gzip'`. The client then compresses large bodies the same way as for API calls.

## Server URL and OpenAPI specification

The Actor server client also gives you the server's URL and its OpenAPI specification:

- <ApiLink to="class/ActorServerClient#get_url">`ActorServerClient.get_url`</ApiLink> returns the server's URL. It fetches and reuses the URL the same way as `request`.
- <ApiLink to="class/ActorServerClient#get_openapi_spec">`ActorServerClient.get_openapi_spec`</ApiLink> returns the server's OpenAPI specification as a `dict`. It's the [web server schema](https://docs.apify.com/platform/actors/development/actor-definition/web-server-schema) from the Actor's definition, taken from the build that serves the server interface. That's the build set in the Actor's `actor_standby.build` field, or `latest` if none is set. If the Actor doesn't define a web server schema, the method returns `None`.

The following example prints the server's URL and the paths of its endpoints:

<Tabs>
<TabItem value="AsyncExample" label="Async client" default>
<CodeBlock className="language-python">
{ServerUrlSpecAsyncExample}
</CodeBlock>
</TabItem>
<TabItem value="SyncExample" label="Sync client">
<CodeBlock className="language-python">
{ServerUrlSpecSyncExample}
</CodeBlock>
</TabItem>
</Tabs>

## Authentication

The request carries the client's API token in the `X-Apify-Authorization` header. The platform removes the token before it forwards the request, so the token doesn't reach the Actor's code.

The headers you pass in `headers` reach the Actor's server as they are. If the Actor reads the `Authorization` header for its own authentication, pass that header in `headers`.

For details, see [How do I authenticate my requests](https://docs.apify.com/platform/actors/running/standby#how-do-i-authenticate-my-requests) in the platform documentation.

## Timeouts, retries, and errors

When no server run is ready, the first request waits for the platform to start one. The method uses the `long` timeout tier by default for that reason. You can pass a different `timeout`, the same way as for API calls. For details, see [Timeouts](./11_timeouts.mdx).

The method returns the server's response whatever its status, so check `status_code` before you use the body. Only rate-limit (429) and Service Unavailable (503) responses are retried, with the client's retry settings. Once the retries run out, the method returns the last response. Network errors and timeouts aren't retried, since the Actor may have already handled the request. For details, see [Retries](./05_retries.mdx).

The method raises only when it doesn't get a response from the server: an `ApifyApiError` when the API can't return the Actor, for example because it doesn't exist, an `ApifyClientError` when it isn't a Server Actor, or the HTTP library's error when the request fails or times out. For details, see [Error handling](./04_error_handling.mdx).
25 changes: 25 additions & 0 deletions docs/02_concepts/code/14_server_async.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from http import HTTPStatus

from apify_client import ApifyClientAsync

TOKEN = 'MY-APIFY-TOKEN'


async def main() -> None:
apify_client = ApifyClientAsync(TOKEN)
actor_server_client = apify_client.actor('username/actor-name').server()

# Send a GET request to the /search endpoint of the Actor's server. The method
# returns error responses too, so check the status first.
response = await actor_server_client.request('/search', params={'q': 'apify'})
if response.status_code != HTTPStatus.OK:
raise RuntimeError(f'Search failed with status {response.status_code}')
print(response.json())

# Send a POST request with a JSON body.
response = await actor_server_client.request(
'/items',
method='POST',
json={'url': 'https://apify.com'},
)
print(response.status_code)
25 changes: 25 additions & 0 deletions docs/02_concepts/code/14_server_sync.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from http import HTTPStatus

from apify_client import ApifyClient

TOKEN = 'MY-APIFY-TOKEN'


def main() -> None:
apify_client = ApifyClient(TOKEN)
actor_server_client = apify_client.actor('username/actor-name').server()

# Send a GET request to the /search endpoint of the Actor's server. The method
# returns error responses too, so check the status first.
response = actor_server_client.request('/search', params={'q': 'apify'})
if response.status_code != HTTPStatus.OK:
raise RuntimeError(f'Search failed with status {response.status_code}')
print(response.json())

# Send a POST request with a JSON body.
response = actor_server_client.request(
'/items',
method='POST',
json={'url': 'https://apify.com'},
)
print(response.status_code)
17 changes: 17 additions & 0 deletions docs/02_concepts/code/14_server_url_spec_async.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from apify_client import ApifyClientAsync

TOKEN = 'MY-APIFY-TOKEN'


async def main() -> None:
apify_client = ApifyClientAsync(TOKEN)
actor_server_client = apify_client.actor('username/actor-name').server()

# Get the URL of the Actor's server.
server_url = await actor_server_client.get_url()
print(server_url)

# Get the OpenAPI specification and list the server's endpoints.
spec = await actor_server_client.get_openapi_spec()
if spec is not None:
print(list(spec['paths']))
17 changes: 17 additions & 0 deletions docs/02_concepts/code/14_server_url_spec_sync.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from apify_client import ApifyClient

TOKEN = 'MY-APIFY-TOKEN'


def main() -> None:
apify_client = ApifyClient(TOKEN)
actor_server_client = apify_client.actor('username/actor-name').server()

# Get the URL of the Actor's server.
server_url = actor_server_client.get_url()
print(server_url)

# Get the OpenAPI specification and list the server's endpoints.
spec = actor_server_client.get_openapi_spec()
if spec is not None:
print(list(spec['paths']))
14 changes: 14 additions & 0 deletions docs/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion scripts/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"""Path to the source code of the apify_client package."""

_SUBSTITUTIONS = [
(re.compile(r'Client'), 'ClientAsync'),
(re.compile(r'Client(?!Error)'), 'ClientAsync'),
(re.compile(r'\bsynchronously\b'), 'asynchronously'),
(re.compile(r'\bSynchronously\b'), 'Asynchronously'),
(re.compile(r'\bsynchronous\b'), 'asynchronous'),
Expand Down
4 changes: 4 additions & 0 deletions src/apify_client/_apify_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
ActorEnvVarClientAsync,
ActorEnvVarCollectionClient,
ActorEnvVarCollectionClientAsync,
ActorServerClient,
ActorServerClientAsync,
ActorVersionClient,
ActorVersionClientAsync,
ActorVersionCollectionClient,
Expand Down Expand Up @@ -188,6 +190,7 @@ def __init__(
actor_collection_client=ActorCollectionClient,
actor_env_var_client=ActorEnvVarClient,
actor_env_var_collection_client=ActorEnvVarCollectionClient,
actor_server_client=ActorServerClient,
actor_version_client=ActorVersionClient,
actor_version_collection_client=ActorVersionCollectionClient,
build_client=BuildClient,
Expand Down Expand Up @@ -567,6 +570,7 @@ def __init__(
actor_collection_client=ActorCollectionClientAsync,
actor_env_var_client=ActorEnvVarClientAsync,
actor_env_var_collection_client=ActorEnvVarCollectionClientAsync,
actor_server_client=ActorServerClientAsync,
actor_version_client=ActorVersionClientAsync,
actor_version_collection_client=ActorVersionCollectionClientAsync,
build_client=BuildClientAsync,
Expand Down
4 changes: 4 additions & 0 deletions src/apify_client/_client_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
ActorEnvVarClientAsync,
ActorEnvVarCollectionClient,
ActorEnvVarCollectionClientAsync,
ActorServerClient,
ActorServerClientAsync,
ActorVersionClient,
ActorVersionClientAsync,
ActorVersionCollectionClient,
Expand Down Expand Up @@ -74,6 +76,7 @@ class ClientRegistry:
actor_collection_client: type[ActorCollectionClient]
actor_env_var_client: type[ActorEnvVarClient]
actor_env_var_collection_client: type[ActorEnvVarCollectionClient]
actor_server_client: type[ActorServerClient]
actor_version_client: type[ActorVersionClient]
actor_version_collection_client: type[ActorVersionCollectionClient]
build_client: type[BuildClient]
Expand Down Expand Up @@ -111,6 +114,7 @@ class ClientRegistryAsync:
actor_collection_client: type[ActorCollectionClientAsync]
actor_env_var_client: type[ActorEnvVarClientAsync]
actor_env_var_collection_client: type[ActorEnvVarCollectionClientAsync]
actor_server_client: type[ActorServerClientAsync]
actor_version_client: type[ActorVersionClientAsync]
actor_version_collection_client: type[ActorVersionCollectionClientAsync]
build_client: type[BuildClientAsync]
Expand Down
3 changes: 3 additions & 0 deletions src/apify_client/_resource_clients/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from .actor_collection import ActorCollectionClient, ActorCollectionClientAsync
from .actor_env_var import ActorEnvVarClient, ActorEnvVarClientAsync
from .actor_env_var_collection import ActorEnvVarCollectionClient, ActorEnvVarCollectionClientAsync
from .actor_server import ActorServerClient, ActorServerClientAsync
from .actor_version import ActorVersionClient, ActorVersionClientAsync
from .actor_version_collection import ActorVersionCollectionClient, ActorVersionCollectionClientAsync
from .build import BuildClient, BuildClientAsync
Expand Down Expand Up @@ -35,6 +36,8 @@
'ActorEnvVarClientAsync',
'ActorEnvVarCollectionClient',
'ActorEnvVarCollectionClientAsync',
'ActorServerClient',
'ActorServerClientAsync',
'ActorVersionClient',
'ActorVersionClientAsync',
'ActorVersionCollectionClient',
Expand Down
82 changes: 82 additions & 0 deletions src/apify_client/_resource_clients/actor.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@

from apify_client._literals import ActorJobStatus, ActorPermissionLevel, RunOrigin
from apify_client._resource_clients import (
ActorServerClient,
ActorServerClientAsync,
ActorVersionClient,
ActorVersionClientAsync,
ActorVersionCollectionClient,
Expand Down Expand Up @@ -86,6 +88,7 @@ def __init__(
resource_path=resource_path,
**kwargs,
)
self._server_client: ActorServerClient | None = None

def get(self, *, timeout: Timeout = 'short') -> ActorResource | None:
"""Retrieve the Actor.
Expand Down Expand Up @@ -625,6 +628,45 @@ def validate_input(

return True

def server(self) -> ActorServerClient:
"""Retrieve a client for the server interface of this Actor.

A Server Actor runs a web server, its server interface, that is called over HTTP without starting a run for each
call. Server Actors were formerly called Actor Standby, and `standby` is a deprecated alias of this method.
Every call returns the same client, so the server URL it fetches is reused.

https://docs.apify.com/platform/actors/running/standby

Returns:
The client for the server interface of this Actor.
"""
if self._server_client is None:
self._server_client = self._client_registry.actor_server_client(
resource_id=self._resource_id,
resource_path=self._resource_path,
base_url=self._base_url,
api_base_url=self._api_base_url,
public_base_url=self._public_base_url,
http_client=self._http_client,
client_registry=self._client_registry,
)
return self._server_client

def standby(self) -> ActorServerClient:
"""Retrieve a client for the server interface of this Actor in Standby mode.

Deprecated alias of `server`, named after Actor Standby, the former name of Server Actors. Will be removed in
v4. Use `server` instead.

https://docs.apify.com/platform/actors/running/standby
"""
warnings.warn(
'`standby` is deprecated and will be removed in v4. Use `server` instead.',
DeprecationWarning,
stacklevel=3,
)
return self.server()


@docs_group('Resource clients')
class ActorClientAsync(ResourceClientAsync):
Expand All @@ -646,6 +688,7 @@ def __init__(
resource_path=resource_path,
**kwargs,
)
self._server_client: ActorServerClientAsync | None = None

async def get(self, *, timeout: Timeout = 'short') -> ActorResource | None:
"""Retrieve the Actor.
Expand Down Expand Up @@ -1185,3 +1228,42 @@ async def validate_input(
)

return True

def server(self) -> ActorServerClientAsync:
"""Retrieve a client for the server interface of this Actor.

A Server Actor runs a web server, its server interface, that is called over HTTP without starting a run for each
call. Server Actors were formerly called Actor Standby, and `standby` is a deprecated alias of this method.
Every call returns the same client, so the server URL it fetches is reused.

https://docs.apify.com/platform/actors/running/standby

Returns:
The client for the server interface of this Actor.
"""
if self._server_client is None:
self._server_client = self._client_registry.actor_server_client(
resource_id=self._resource_id,
resource_path=self._resource_path,
base_url=self._base_url,
api_base_url=self._api_base_url,
public_base_url=self._public_base_url,
http_client=self._http_client,
client_registry=self._client_registry,
)
return self._server_client

def standby(self) -> ActorServerClientAsync:
"""Retrieve a client for the server interface of this Actor in Standby mode.

Deprecated alias of `server`, named after Actor Standby, the former name of Server Actors. Will be removed in
v4. Use `server` instead.

https://docs.apify.com/platform/actors/running/standby
"""
warnings.warn(
'`standby` is deprecated and will be removed in v4. Use `server` instead.',
DeprecationWarning,
stacklevel=3,
)
return self.server()
Loading
Loading