diff --git a/docs/02_concepts/11_pay_per_event.mdx b/docs/02_concepts/11_pay_per_event.mdx
index 5c961460..217ab0e9 100644
--- a/docs/02_concepts/11_pay_per_event.mdx
+++ b/docs/02_concepts/11_pay_per_event.mdx
@@ -1,84 +1,95 @@
---
id: pay-per-event
title: Pay-per-event monetization
-description: Monetize your Actors using the pay-per-event pricing model
+description: Monetize your Actors using the pay-per-event pricing model.
---
import ActorChargeSource from '!!raw-loader!roa-loader!./code/11_actor_charge.py';
import ConditionalActorChargeSource from '!!raw-loader!roa-loader!./code/11_conditional_actor_charge.py';
import ChargeLimitCheckSource from '!!raw-loader!roa-loader!./code/11_charge_limit_check.py';
import AdvancedChargingExample from '!!raw-loader!roa-loader!./code/11_advanced_charging.py';
+import ChargePerUnitSource from '!!raw-loader!roa-loader!./code/11_charge_per_unit.py';
import ApiLink from '@theme/ApiLink';
import RunnableCodeBlock from '@site/src/components/RunnableCodeBlock';
-Apify provides several [pricing models](https://docs.apify.com/platform/actors/publishing/monetize) for monetizing your Actors. The most recent and most flexible one is [pay-per-event](https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event), which lets you charge your users programmatically directly from your Actor. As the name suggests, you may charge the users each time a specific event occurs, for example a call to an external API or when you return a result.
+With the [pay-per-event pricing model](https://docs.apify.com/platform/actors/publishing/monetize/pay-per-event), users pay for specific events that are programmatically triggered from your Actor's source code. Such events might include, for example, generating a single result or calling an external API.
-To use the pay-per-event pricing model, first [set it up](https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event) for your Actor in the [Apify Console](https://docs.apify.com/platform/console). After that, you can start charging for events.
+## Configure monetization
-:::info How pay-per-event pricing works
+To use the pay-per-event pricing model and define the pricing, [monetize your Actor](https://docs.apify.com/platform/actors/publishing/monetize) in Apify Console.
-If you want more details about PPE pricing, please refer to our [PPE documentation](https://docs.apify.com/platform/actors/publishing/monetize/pay-per-event).
+## Charge for events
-:::
-
-## Charging for events
-
-After monetization is set in the Apify Console, you can add `Actor.charge` calls to your code and start monetizing!
+To charge for events, use the `Actor.charge` method. It records that your Actor performed a billable activity, so the Apify platform charges the user's account for it.
{ActorChargeSource}
-Then you just push your code to Apify and that's it! The SDK will even keep track of the max total charge setting for you, so you will not provide more value than what the user chose to pay for.
-
-If you need finer control over charging, you can call `Actor.get_charging_manager()` to access the `ChargingManager`, which can provide more detailed information, for example how many events of each type can be charged before reaching the configured limit.
+For details on how to maximize your profits, see also [Best practices](https://docs.apify.com/platform/actors/publishing/monetize/pay-per-event#best-practices).
-### Handling the charge limit
+### Prefer per-unit charging
-While the SDK automatically prevents overcharging by limiting how many events are charged and how many items are pushed, **it does not stop your Actor from running**. When the charge limit is reached, `Actor.charge` and `Actor.push_data` will silently stop charging and pushing data, but your Actor will keep running — potentially doing expensive work (scraping pages, calling APIs) for no purpose. This means your Actor may never terminate on its own if you don't check the charge limit yourself.
-
-To avoid this, you should check the `event_charge_limit_reached` field in the result returned by `Actor.charge` or `Actor.push_data` and stop your Actor when the limit is reached. You can also use the `chargeable_within_limit` field from the result to plan ahead — it tells you how many events of each type can still be charged within the remaining budget.
+If you can split your work into individual units, for example scraping one page or calling one API endpoint, prefer issuing one `Actor.charge()` call per unit. Don't batch multiple events into a single call with the `count` parameter. This approach gives you better control over budget consumption:
- {ChargeLimitCheckSource}
+ {ChargePerUnitSource}
-Alternatively, you can periodically check the remaining budget via `Actor.get_charging_manager()` instead of inspecting every `ChargeResult`. This can be useful when charging happens in multiple places across your code, or when using a crawler where you don't directly control the main loop.
+If you use the `count` parameter, always check the returned `charged_count`. It tells you how many events were charged, which may be less than what you requested.
-:::caution
-Always check the charge limit in your Actor, whether through `ChargeResult` return values or the `ChargingManager`. Without this check, your Actor will continue running and consuming platform resources after the budget is exhausted, producing no output.
-:::
+### Monitor charging
-## Advanced charging management
+For both custom and synthetic events, every `Actor.charge` call returns a `ChargeResult`. Inspect its fields to learn how much was charged.
-For budget-aware crawling strategies, the `ChargingManager` (accessed via `Actor.get_charging_manager()`) provides methods for querying the remaining budget, total charged amount, and per-event charge counts. This lets you plan work based on the remaining budget rather than discovering the limit after the fact. See the `ChargingManager` API reference for the full list of available methods.
+Instead of inspecting every `ChargeResult`, you can also use the `ChargingManager`. It provides methods for querying the remaining budget, total charged amount, and per-event charge counts.
+
+This information lets you plan work based on the remaining budget rather than discovering the limit after the fact. It's particularly useful when charging happens in multiple places across your code, or when using a crawler where you don't directly control the main loop.
+
+To access the `ChargingManager`, use the `Actor.get_charging_manager()` method:
{AdvancedChargingExample}
-## Transitioning from a different pricing model
+### Handle the charge limit
-When you plan to start using the pay-per-event pricing model for an Actor that is already monetized with a different pricing model, your source code must support both pricing models during the transition period enforced by the Apify platform. The most frequent case is the transition from the pay-per-result model which utilizes the `ACTOR_MAX_PAID_DATASET_ITEMS` environment variable to prevent returning unpaid dataset items. The following is an example how to handle such scenarios. The key part is the `ChargingManager.get_pricing_info()` method which returns information about the current pricing model.
+Your Actor users can set the maximum cost for the run. It helps users control their spending, as they won't be billed beyond the limit.
+
+The user spending limit for the run is available in your Actor code as the `ACTOR_MAX_TOTAL_CHARGE_USD` environment variable, and `ChargeResult` already accounts for it. To control the limit, inspect the fields of `ChargeResult`:
+
+- `event_charge_limit_reached`: Checks if the user's limit allows for another charge of the event.
+- `chargeable_within_limit`: Indicates how many events of each type can still be charged within the remaining budget.
+- `charged_count`: Indicates how many events were billed by the call.
- {ConditionalActorChargeSource}
+ {ChargeLimitCheckSource}
-## Local development
+When the charge limit is reached, `Actor.charge` stops charging and `Actor.push_data` stops pushing data. The platform then aborts the run automatically. However, the run keeps consuming platform resources for a short time before it stops. For details, see [Handle graceful shutdown](https://docs.apify.com/platform/actors/publishing/monetize/pay-per-event#handle-graceful-shutdown).
+
+## Test monetization locally
-It is encouraged to test your monetization code on your machine before releasing it to the public. To tell your Actor that it should work in pay-per-event mode, pass it the `ACTOR_TEST_PAY_PER_EVENT` environment variable:
+Before releasing your monetization code to the public, test it locally. To make your Actor work in pay-per-event mode, pass it the `ACTOR_TEST_PAY_PER_EVENT` environment variable:
```sh
ACTOR_TEST_PAY_PER_EVENT=true python -m youractor
```
-If you also wish to see a log of all the events charged throughout the run, the Apify SDK keeps a log of charged events in a so called charging dataset. Your charging dataset can be found under the `charging-log` name (unless you change your storage settings, this dataset is stored in `storage/datasets/charging-log/`). Please note that this log is not available when running the Actor in production on the Apify platform.
+In this mode, nothing is billed, but every charge call is logged into a local `charging-log` dataset.
+
+### View the log
+
+To inspect the results of your tests, open the `charging-log` dataset. By default, it's stored in the `storage/datasets/charging-log/` directory.
-Because pricing configuration is stored by the Apify platform, all events will have a default price of $1.
+The log contains all the events charged throughout the run. Because pricing configuration is stored by the Apify platform, all events have a default price of $1.
-## Conclusion
+Note that this log isn't available when running the Actor in production on the Apify platform.
-This page has covered the pay-per-event pricing model: charging for events with `Actor.charge`, respecting the charge limit so your Actor stops once the budget is exhausted, querying the `ChargingManager`, handling the transition from another pricing model, and testing charging locally.
+## Transition from a different pricing model
-For comprehensive details on pay-per-event pricing and Actor monetization, see the [pay-per-event](https://docs.apify.com/platform/actors/publishing/monetize/pay-per-event) and [monetization](https://docs.apify.com/platform/actors/publishing/monetize) documentation on the Apify platform.
+When you plan to start using the pay-per-event pricing model for an Actor that is already monetized with a different pricing model, your source code must support both pricing models during the transition period enforced by the Apify platform. The most frequent case is the transition from the pay-per-result model which utilizes the `ACTOR_MAX_PAID_DATASET_ITEMS` environment variable to prevent returning unpaid dataset items. The following is an example how to handle such scenarios. The key part is the `ChargingManager.get_pricing_info()` method which returns information about the current pricing model.
+
+
+ {ConditionalActorChargeSource}
+
diff --git a/docs/02_concepts/code/11_charge_limit_check.py b/docs/02_concepts/code/11_charge_limit_check.py
index 20bca7cd..f3f407a1 100644
--- a/docs/02_concepts/code/11_charge_limit_check.py
+++ b/docs/02_concepts/code/11_charge_limit_check.py
@@ -16,7 +16,7 @@ async def main() -> None:
result = {'url': url, 'data': f'Scraped data from {url}'}
# highlight-start
- # push_data returns a ChargeResult - check it to know if the budget ran out
+ # push_data returns a ChargeResult, check it to know if the budget ran out
charge_result = await Actor.push_data(
result, charged_event_name='result-item'
)
diff --git a/docs/02_concepts/code/11_charge_per_unit.py b/docs/02_concepts/code/11_charge_per_unit.py
new file mode 100644
index 00000000..d5b53239
--- /dev/null
+++ b/docs/02_concepts/code/11_charge_per_unit.py
@@ -0,0 +1,30 @@
+import asyncio
+
+from apify import Actor
+
+
+async def main() -> None:
+ async with Actor:
+ urls = [
+ 'https://example.com/1',
+ 'https://example.com/2',
+ 'https://example.com/3',
+ ]
+
+ for url in urls:
+ # Charge for a single event
+ charge_result = await Actor.charge(
+ event_name='page-scraped',
+ )
+
+ if charge_result.event_charge_limit_reached:
+ break
+
+ result = {'url': url, 'data': f'Scraped data from {url}'}
+
+ # Push the result to the dataset
+ await Actor.push_data(result)
+
+
+if __name__ == '__main__':
+ asyncio.run(main())
diff --git a/website/versioned_docs/version-4.0/02_concepts/11_pay_per_event.mdx b/website/versioned_docs/version-4.0/02_concepts/11_pay_per_event.mdx
index 5c961460..217ab0e9 100644
--- a/website/versioned_docs/version-4.0/02_concepts/11_pay_per_event.mdx
+++ b/website/versioned_docs/version-4.0/02_concepts/11_pay_per_event.mdx
@@ -1,84 +1,95 @@
---
id: pay-per-event
title: Pay-per-event monetization
-description: Monetize your Actors using the pay-per-event pricing model
+description: Monetize your Actors using the pay-per-event pricing model.
---
import ActorChargeSource from '!!raw-loader!roa-loader!./code/11_actor_charge.py';
import ConditionalActorChargeSource from '!!raw-loader!roa-loader!./code/11_conditional_actor_charge.py';
import ChargeLimitCheckSource from '!!raw-loader!roa-loader!./code/11_charge_limit_check.py';
import AdvancedChargingExample from '!!raw-loader!roa-loader!./code/11_advanced_charging.py';
+import ChargePerUnitSource from '!!raw-loader!roa-loader!./code/11_charge_per_unit.py';
import ApiLink from '@theme/ApiLink';
import RunnableCodeBlock from '@site/src/components/RunnableCodeBlock';
-Apify provides several [pricing models](https://docs.apify.com/platform/actors/publishing/monetize) for monetizing your Actors. The most recent and most flexible one is [pay-per-event](https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event), which lets you charge your users programmatically directly from your Actor. As the name suggests, you may charge the users each time a specific event occurs, for example a call to an external API or when you return a result.
+With the [pay-per-event pricing model](https://docs.apify.com/platform/actors/publishing/monetize/pay-per-event), users pay for specific events that are programmatically triggered from your Actor's source code. Such events might include, for example, generating a single result or calling an external API.
-To use the pay-per-event pricing model, first [set it up](https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event) for your Actor in the [Apify Console](https://docs.apify.com/platform/console). After that, you can start charging for events.
+## Configure monetization
-:::info How pay-per-event pricing works
+To use the pay-per-event pricing model and define the pricing, [monetize your Actor](https://docs.apify.com/platform/actors/publishing/monetize) in Apify Console.
-If you want more details about PPE pricing, please refer to our [PPE documentation](https://docs.apify.com/platform/actors/publishing/monetize/pay-per-event).
+## Charge for events
-:::
-
-## Charging for events
-
-After monetization is set in the Apify Console, you can add `Actor.charge` calls to your code and start monetizing!
+To charge for events, use the `Actor.charge` method. It records that your Actor performed a billable activity, so the Apify platform charges the user's account for it.
{ActorChargeSource}
-Then you just push your code to Apify and that's it! The SDK will even keep track of the max total charge setting for you, so you will not provide more value than what the user chose to pay for.
-
-If you need finer control over charging, you can call `Actor.get_charging_manager()` to access the `ChargingManager`, which can provide more detailed information, for example how many events of each type can be charged before reaching the configured limit.
+For details on how to maximize your profits, see also [Best practices](https://docs.apify.com/platform/actors/publishing/monetize/pay-per-event#best-practices).
-### Handling the charge limit
+### Prefer per-unit charging
-While the SDK automatically prevents overcharging by limiting how many events are charged and how many items are pushed, **it does not stop your Actor from running**. When the charge limit is reached, `Actor.charge` and `Actor.push_data` will silently stop charging and pushing data, but your Actor will keep running — potentially doing expensive work (scraping pages, calling APIs) for no purpose. This means your Actor may never terminate on its own if you don't check the charge limit yourself.
-
-To avoid this, you should check the `event_charge_limit_reached` field in the result returned by `Actor.charge` or `Actor.push_data` and stop your Actor when the limit is reached. You can also use the `chargeable_within_limit` field from the result to plan ahead — it tells you how many events of each type can still be charged within the remaining budget.
+If you can split your work into individual units, for example scraping one page or calling one API endpoint, prefer issuing one `Actor.charge()` call per unit. Don't batch multiple events into a single call with the `count` parameter. This approach gives you better control over budget consumption:
- {ChargeLimitCheckSource}
+ {ChargePerUnitSource}
-Alternatively, you can periodically check the remaining budget via `Actor.get_charging_manager()` instead of inspecting every `ChargeResult`. This can be useful when charging happens in multiple places across your code, or when using a crawler where you don't directly control the main loop.
+If you use the `count` parameter, always check the returned `charged_count`. It tells you how many events were charged, which may be less than what you requested.
-:::caution
-Always check the charge limit in your Actor, whether through `ChargeResult` return values or the `ChargingManager`. Without this check, your Actor will continue running and consuming platform resources after the budget is exhausted, producing no output.
-:::
+### Monitor charging
-## Advanced charging management
+For both custom and synthetic events, every `Actor.charge` call returns a `ChargeResult`. Inspect its fields to learn how much was charged.
-For budget-aware crawling strategies, the `ChargingManager` (accessed via `Actor.get_charging_manager()`) provides methods for querying the remaining budget, total charged amount, and per-event charge counts. This lets you plan work based on the remaining budget rather than discovering the limit after the fact. See the `ChargingManager` API reference for the full list of available methods.
+Instead of inspecting every `ChargeResult`, you can also use the `ChargingManager`. It provides methods for querying the remaining budget, total charged amount, and per-event charge counts.
+
+This information lets you plan work based on the remaining budget rather than discovering the limit after the fact. It's particularly useful when charging happens in multiple places across your code, or when using a crawler where you don't directly control the main loop.
+
+To access the `ChargingManager`, use the `Actor.get_charging_manager()` method:
{AdvancedChargingExample}
-## Transitioning from a different pricing model
+### Handle the charge limit
-When you plan to start using the pay-per-event pricing model for an Actor that is already monetized with a different pricing model, your source code must support both pricing models during the transition period enforced by the Apify platform. The most frequent case is the transition from the pay-per-result model which utilizes the `ACTOR_MAX_PAID_DATASET_ITEMS` environment variable to prevent returning unpaid dataset items. The following is an example how to handle such scenarios. The key part is the `ChargingManager.get_pricing_info()` method which returns information about the current pricing model.
+Your Actor users can set the maximum cost for the run. It helps users control their spending, as they won't be billed beyond the limit.
+
+The user spending limit for the run is available in your Actor code as the `ACTOR_MAX_TOTAL_CHARGE_USD` environment variable, and `ChargeResult` already accounts for it. To control the limit, inspect the fields of `ChargeResult`:
+
+- `event_charge_limit_reached`: Checks if the user's limit allows for another charge of the event.
+- `chargeable_within_limit`: Indicates how many events of each type can still be charged within the remaining budget.
+- `charged_count`: Indicates how many events were billed by the call.
- {ConditionalActorChargeSource}
+ {ChargeLimitCheckSource}
-## Local development
+When the charge limit is reached, `Actor.charge` stops charging and `Actor.push_data` stops pushing data. The platform then aborts the run automatically. However, the run keeps consuming platform resources for a short time before it stops. For details, see [Handle graceful shutdown](https://docs.apify.com/platform/actors/publishing/monetize/pay-per-event#handle-graceful-shutdown).
+
+## Test monetization locally
-It is encouraged to test your monetization code on your machine before releasing it to the public. To tell your Actor that it should work in pay-per-event mode, pass it the `ACTOR_TEST_PAY_PER_EVENT` environment variable:
+Before releasing your monetization code to the public, test it locally. To make your Actor work in pay-per-event mode, pass it the `ACTOR_TEST_PAY_PER_EVENT` environment variable:
```sh
ACTOR_TEST_PAY_PER_EVENT=true python -m youractor
```
-If you also wish to see a log of all the events charged throughout the run, the Apify SDK keeps a log of charged events in a so called charging dataset. Your charging dataset can be found under the `charging-log` name (unless you change your storage settings, this dataset is stored in `storage/datasets/charging-log/`). Please note that this log is not available when running the Actor in production on the Apify platform.
+In this mode, nothing is billed, but every charge call is logged into a local `charging-log` dataset.
+
+### View the log
+
+To inspect the results of your tests, open the `charging-log` dataset. By default, it's stored in the `storage/datasets/charging-log/` directory.
-Because pricing configuration is stored by the Apify platform, all events will have a default price of $1.
+The log contains all the events charged throughout the run. Because pricing configuration is stored by the Apify platform, all events have a default price of $1.
-## Conclusion
+Note that this log isn't available when running the Actor in production on the Apify platform.
-This page has covered the pay-per-event pricing model: charging for events with `Actor.charge`, respecting the charge limit so your Actor stops once the budget is exhausted, querying the `ChargingManager`, handling the transition from another pricing model, and testing charging locally.
+## Transition from a different pricing model
-For comprehensive details on pay-per-event pricing and Actor monetization, see the [pay-per-event](https://docs.apify.com/platform/actors/publishing/monetize/pay-per-event) and [monetization](https://docs.apify.com/platform/actors/publishing/monetize) documentation on the Apify platform.
+When you plan to start using the pay-per-event pricing model for an Actor that is already monetized with a different pricing model, your source code must support both pricing models during the transition period enforced by the Apify platform. The most frequent case is the transition from the pay-per-result model which utilizes the `ACTOR_MAX_PAID_DATASET_ITEMS` environment variable to prevent returning unpaid dataset items. The following is an example how to handle such scenarios. The key part is the `ChargingManager.get_pricing_info()` method which returns information about the current pricing model.
+
+
+ {ConditionalActorChargeSource}
+
diff --git a/website/versioned_docs/version-4.0/02_concepts/code/11_charge_per_unit.py b/website/versioned_docs/version-4.0/02_concepts/code/11_charge_per_unit.py
new file mode 100644
index 00000000..d5b53239
--- /dev/null
+++ b/website/versioned_docs/version-4.0/02_concepts/code/11_charge_per_unit.py
@@ -0,0 +1,30 @@
+import asyncio
+
+from apify import Actor
+
+
+async def main() -> None:
+ async with Actor:
+ urls = [
+ 'https://example.com/1',
+ 'https://example.com/2',
+ 'https://example.com/3',
+ ]
+
+ for url in urls:
+ # Charge for a single event
+ charge_result = await Actor.charge(
+ event_name='page-scraped',
+ )
+
+ if charge_result.event_charge_limit_reached:
+ break
+
+ result = {'url': url, 'data': f'Scraped data from {url}'}
+
+ # Push the result to the dataset
+ await Actor.push_data(result)
+
+
+if __name__ == '__main__':
+ asyncio.run(main())