diff --git a/sources/platform/actors/publishing/monetize/pay_per_event.mdx b/sources/platform/actors/publishing/monetize/pay_per_event.mdx
index 313f282533..e2cb6024aa 100644
--- a/sources/platform/actors/publishing/monetize/pay_per_event.mdx
+++ b/sources/platform/actors/publishing/monetize/pay_per_event.mdx
@@ -75,83 +75,10 @@ Use the **Pay per event + usage** toggle to enable or disable this option in the
The option can be turned off at any time with immediate effect, as it is a positive change for the user. Turning it on will take 14 days.
-## Respect user spending limits
-
-Finish the Actor run once charging reaches user-configured maximum cost per run. Apify SDKs (JS and Python) return `ChargeResult` that helps determine when to finish.
-
-The `eventChargeLimitReached` property checks if the user's limit allows for another charge of this event. If you have multiple events, analyze the `chargeableWithinLimit` property to see if other events can still be charged before stopping the Actor.
-
-:::info ACTOR_MAX_TOTAL_CHARGE_USD environment variable
-
-For pay-per-event Actors, users set a spending limit through the Apify Console. This limit is available in your Actor code as the `ACTOR_MAX_TOTAL_CHARGE_USD` [environment variable](/platform/actors/development/programming-interface/environment-variables), which contains the user's maximum cost.
-The Apify SDK's `ChargeResult` respects the user set limit already.
-
-:::
-
-
-
-
-```js
-import { Actor } from 'apify';
-
-const chargeForApiProductDetail = async () => {
- const chargeResult = await Actor.charge({ eventName: "product-detail" });
-
- return chargeResult;
-};
-
-await Actor.init();
-
-// API call, or any other logic that you want to charge for
-const chargeResult = await chargeForApiProductDetail();
-
-if (chargeResult.eventChargeLimitReached) {
- await Actor.exit();
-}
-
-// Rest of the Actor logic
-
-await Actor.exit();
-```
-
-
-
-
-```py
-from apify import Actor
-
-async def charge_for_api_product_detail():
- charge_result = await Actor.charge(event_name='product-detail')
-
- return charge_result
-
-async def main():
- await Actor.init()
-
- # API call, or any other logic that you want to charge for
-
- charge_result = await charge_for_api_product_detail()
-
- if charge_result.event_charge_limit_reached:
- await Actor.exit()
-
- # Rest of the Actor logic
-
- await Actor.exit()
-```
-
-
-
-
-:::note Crawlee integration and spending limits
-
-When using [Crawlee](https://crawlee.dev/), use `crawler.autoscaledPool.abort()` instead of `Actor.exit()` to gracefully finish the crawler and allow the rest of your code to process normally.
-
-:::
## Best practices for PPE Actors
-Use our [SDKs](/sdk) (JS and, Python or use [`apify actor charge`](/cli/docs/next/reference#apify-actor-charge-eventname) when using our Apify CLI) to simplify PPE implementation into your Actor. SDKs help you handle pricing, usage tracking, idempotency keys, API errors, and, event charging via an API. You can also choose not to use it, but then you must handle API integration and possible edge cases manually.
+Use the Apify [SDKs](/sdk) (JS and Python) or the [`apify actor charge`](/cli/docs/next/reference#apify-actor-charge-eventname) CLI command to simplify PPE implementation. SDKs handle pricing, usage tracking, idempotency keys, API errors, and event charging. You can also call the [PPE charging API](/api/v2/post-charge-run) directly, but then you must handle API integration and edge cases manually.
### Use synthetic start event `apify-actor-start`
@@ -231,6 +158,192 @@ When using browser automation tools like Puppeteer or Playwright for web scrapin
:::
+### Respect user spending limits
+
+Finish the Actor run once charging reaches the user-configured maximum cost per run. `Actor.charge()` returns a `ChargeResult` object that helps determine when to stop.
+
+The `eventChargeLimitReached` property checks if the user's limit allows for another charge of this event.
+
+:::info ACTOR_MAX_TOTAL_CHARGE_USD environment variable
+
+Users set a spending limit through the Apify Console. This limit is available in your Actor code as the `ACTOR_MAX_TOTAL_CHARGE_USD` [environment variable](/platform/actors/development/programming-interface/environment-variables). The Apify SDK's `ChargeResult` respects this limit automatically.
+
+:::
+
+When using [Crawlee](https://crawlee.dev/), use `crawler.autoscaledPool.abort()` instead of `Actor.exit()` to gracefully finish the crawler.
+
+### Keep pricing simple with fewer events
+
+Try to limit the number of events. Fewer events make it easier for users to understand your pricing and predict their costs.
+
+### Make events produce visible results
+
+For Actors that produce data, events should map to something concrete in the user's dataset or storage.
+
+However, we acknowledge that some events don't produce tangible results (such as running AI workflows or processing external API calls). This flexibility gives you the freedom to charge for special operations, complex workflows, and unique value propositions.
+
+Examples:
+
+- _`post` event_: Each charge adds one social media post to the dataset
+- _`profile` event_: Each charge adds one user profile to the dataset
+- _`processed-image` event_: Each charge adds one processed image to the dataset
+- _`ai-analysis` event_: Each charge processes one document through an AI workflow (no tangible output, but valuable processing)
+
+:::note Additional context
+
+You can display a status message or push a record to the dataset to inform users about non-data actions performed by your Actor. This helps users understand what actions were charged for, even if those actions do not produce tangible output.
+
+:::
+
+### Use idempotency keys to prevent double charges
+
+If you're not using the Apify SDKs (JS/Python), you need to handle idempotency (ensuring the same operation produces the same result when called multiple times) manually to prevent charging the same event multiple times.
+
+## Code examples
+
+The following examples show common charging patterns using the Apify SDK. Use the [JS](/sdk/js/reference/class/Actor#charge) or [Python](/sdk/python/reference/class/Actor#charge) SDK to handle idempotency, API errors, and spending limits automatically.
+
+### Charge per result
+
+Charge an event when your Actor produces a data item and check the spending limit before continuing.
+
+
+
+
+```js
+import { Actor } from 'apify';
+
+await Actor.init();
+
+const data = { url: 'https://example.com', title: 'Example' };
+const chargeResult = await Actor.charge({ eventName: 'result' });
+
+if (chargeResult.eventChargeLimitReached) {
+ await Actor.exit();
+}
+
+await Actor.pushData(data);
+
+await Actor.exit();
+```
+
+
+
+
+```python
+from apify import Actor
+
+async def main():
+ await Actor.init()
+
+ data = {'url': 'https://example.com', 'title': 'Example'}
+ charge_result = await Actor.charge(event_name='result')
+
+ if charge_result.event_charge_limit_reached:
+ await Actor.exit()
+
+ await Actor.push_data(data)
+
+ await Actor.exit()
+```
+
+
+
+
+### Charge for multiple event types
+
+Charge for multiple event types in one Actor run. Each event type must be defined in your Actor's pricing configuration.
+
+
+
+
+```js
+import { Actor } from 'apify';
+
+await Actor.init();
+
+// Charge for the scraped data
+await Actor.charge({ eventName: 'result' });
+
+// Charge for additional processing
+await Actor.charge({ eventName: 'filter' });
+
+await Actor.pushData({ url: 'https://example.com', title: 'Example' });
+
+await Actor.exit();
+```
+
+
+
+
+```python
+from apify import Actor
+
+async def main():
+ await Actor.init()
+
+ # Charge for the scraped data
+ await Actor.charge(event_name='result')
+
+ # Charge for additional processing
+ await Actor.charge(event_name='filter')
+
+ await Actor.push_data({'url': 'https://example.com', 'title': 'Example'})
+
+ await Actor.exit()
+```
+
+
+
+
+### Charge for multiple items at once
+
+Use the `count` parameter to charge for a batch of items in a single call. The returned `chargedCount` may be lower than requested if the user's spending limit is reached, so use it to determine how many items to push.
+
+
+
+
+```js
+import { Actor } from 'apify';
+
+await Actor.init();
+
+const results = [
+ { url: 'https://example.com/1', title: 'Page 1' },
+ { url: 'https://example.com/2', title: 'Page 2' },
+ { url: 'https://example.com/3', title: 'Page 3' },
+];
+
+const chargeResult = await Actor.charge({ eventName: 'result', count: results.length });
+await Actor.pushData(results.slice(0, chargeResult.chargedCount));
+
+await Actor.exit();
+```
+
+
+
+
+```python
+from apify import Actor
+
+async def main():
+ await Actor.init()
+
+ results = [
+ {'url': 'https://example.com/1', 'title': 'Page 1'},
+ {'url': 'https://example.com/2', 'title': 'Page 2'},
+ {'url': 'https://example.com/3', 'title': 'Page 3'},
+ ]
+
+ charge_result = await Actor.charge(event_name='result', count=len(results))
+ await Actor.push_data(results[:charge_result.charged_count])
+
+ await Actor.exit()
+```
+
+
+
+
### Charge for invalid input
Charge for things like URLs that appear valid but lead to errors (like 404s) since you had to open the page to discover the error. Return error items with proper error codes and messages instead of failing the entire Actor run.
@@ -313,32 +426,113 @@ async def main():
-### Keep pricing simple with fewer events
+### Respect spending limits
-Try to limit the number of events. Fewer events make it easier for users to understand your pricing and predict their costs.
+Check `eventChargeLimitReached` after each charge and stop the Actor when the user's budget is exhausted. For Actors with multiple event types, use `chargeableWithinLimit` to check whether any event can still be charged before stopping.
-### Make events produce visible results
+
+
-For Actors that produce data, events should map to something concrete in the user's dataset or storage.
+```js
+import { Actor } from 'apify';
-However, we acknowledge that some events don't produce tangible results (such as running AI workflows or processing external API calls). This flexibility gives you the freedom to charge for special operations, complex workflows, and unique value propositions.
+await Actor.init();
-Examples:
+const input = await Actor.getInput();
+const { items } = input;
-- _`post` event_: Each charge adds one social media post to the dataset
-- _`profile` event_: Each charge adds one user profile to the dataset
-- _`processed-image` event_: Each charge adds one processed image to the dataset
-- _`ai-analysis` event_: Each charge processes one document through an AI workflow (no tangible output, but valuable processing)
+for (const item of items) {
+ const chargeResult = await Actor.charge({ eventName: 'result' });
-:::note Additional context
+ if (chargeResult.eventChargeLimitReached) {
+ await Actor.exit('Spending limit reached');
+ }
-You can display a status message or push a record to the dataset to inform users about non-data actions performed by your Actor. This helps users understand what actions were charged for, even if those actions do not produce tangible output.
+ await Actor.pushData(item);
+}
-:::
+await Actor.exit();
+```
-### Use idempotency keys to prevent double charges
+
+
-If you're not using the Apify SDKs (JS/Python), you need to handle idempotency (ensuring the same operation produces the same result when called multiple times) manually to prevent charging the same event multiple times.
+```python
+from apify import Actor
+
+async def main():
+ await Actor.init()
+
+ input_data = await Actor.get_input()
+ items = input_data.get('items', [])
+
+ for item in items:
+ charge_result = await Actor.charge(event_name='result')
+
+ if charge_result.event_charge_limit_reached:
+ await Actor.exit('Spending limit reached')
+
+ await Actor.push_data(item)
+
+ await Actor.exit()
+```
+
+
+
+
+When charging multiple event types, use `chargeableWithinLimit` to check whether any event can still be charged before stopping.
+
+
+
+
+```js
+import { Actor } from 'apify';
+
+await Actor.init();
+
+const input = await Actor.getInput();
+const { items } = input;
+
+for (const item of items) {
+ const chargeResult = await Actor.charge({ eventName: 'result' });
+
+ // chargeableWithinLimit lists event types the user's budget still covers
+ if (!chargeResult.chargeableWithinLimit.includes('result')) {
+ await Actor.exit('Spending limit reached');
+ }
+
+ await Actor.pushData(item);
+}
+
+await Actor.exit();
+```
+
+
+
+
+```python
+from apify import Actor
+
+async def main():
+ await Actor.init()
+
+ input_data = await Actor.get_input()
+ items = input_data.get('items', [])
+
+ for item in items:
+ charge_result = await Actor.charge(event_name='result')
+
+ # chargeable_within_limit lists event types the user's budget still covers
+ if 'result' not in charge_result.chargeable_within_limit:
+ await Actor.exit('Spending limit reached')
+
+ await Actor.push_data(item)
+
+ await Actor.exit()
+```
+
+
+
## Example of a PPE pricing