From e30d80f8c40866e2ecb8e7da47739a968cd89bc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Olender?= <92638966+TC-MO@users.noreply.github.com> Date: Tue, 17 Feb 2026 22:40:27 +0100 Subject: [PATCH 1/2] docs: add PPE event charging examples and restructure best practices Add three generic Actor.charge() examples (JS + Python) to the PPE docs: - Charge per result with spending limit check - Charge for multiple event types - Charge for multiple items at once using count parameter Move "Respect user spending limits" from standalone section into best practices and reorder subsections for logical flow: setup, spending limits, charging patterns, then pricing guidance. Clean up best practices intro prose. Co-Authored-By: Claude Opus 4.6 --- .../publishing/monetize/pay_per_event.mdx | 230 ++++++++++++------ 1 file changed, 156 insertions(+), 74 deletions(-) diff --git a/sources/platform/actors/publishing/monetize/pay_per_event.mdx b/sources/platform/actors/publishing/monetize/pay_per_event.mdx index 313f282533..ff18b25330 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,161 @@ 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. If you have multiple events, use the `chargeableWithinLimit` property to see if other events can still be charged before stopping the Actor. + +:::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. + +::: + +### 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 multiple event types in a single operation. 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. + + + + +```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' }, +]; + +await Actor.charge({ eventName: 'result', count: results.length }); +await Actor.pushData(results); + +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'}, + ] + + await Actor.charge(event_name='result', count=len(results)) + await Actor.push_data(results) + + 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. From 00ecad4abda6677e0b2612e07100cded2f0cdaf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Olender?= <92638966+TC-MO@users.noreply.github.com> Date: Thu, 19 Feb 2026 23:12:51 +0100 Subject: [PATCH 2/2] docs: address PR #2263 review feedback on PPE examples - Fix "single operation" wording to "in one Actor run" - Move Crawlee note out of ACTOR_MAX_TOTAL_CHARGE_USD admonition into prose - Removed chargeableWithinLimit mention from prose - Add chargedCount handling to batch charging example Co-Authored-By: Claude Opus 4.6 --- .../publishing/monetize/pay_per_event.mdx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/sources/platform/actors/publishing/monetize/pay_per_event.mdx b/sources/platform/actors/publishing/monetize/pay_per_event.mdx index ff18b25330..ff5863a48e 100644 --- a/sources/platform/actors/publishing/monetize/pay_per_event.mdx +++ b/sources/platform/actors/publishing/monetize/pay_per_event.mdx @@ -162,16 +162,16 @@ When using browser automation tools like Puppeteer or Playwright for web scrapin 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. If you have multiple events, use the `chargeableWithinLimit` property to see if other events can still be charged before stopping the Actor. +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. - ::: +When using [Crawlee](https://crawlee.dev/), use `crawler.autoscaledPool.abort()` instead of `Actor.exit()` to gracefully finish the crawler. + ### Charge per result Charge an event when your Actor produces a data item and check the spending limit before continuing. @@ -221,7 +221,7 @@ async def main(): ### Charge for multiple event types -Charge multiple event types in a single operation. Each event type must be defined in your Actor's pricing configuration. +Charge for multiple event types in one Actor run. Each event type must be defined in your Actor's pricing configuration. @@ -267,7 +267,7 @@ async def main(): ### Charge for multiple items at once -Use the `count` parameter to charge for a batch of items in a single call. +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. @@ -283,8 +283,8 @@ const results = [ { url: 'https://example.com/3', title: 'Page 3' }, ]; -await Actor.charge({ eventName: 'result', count: results.length }); -await Actor.pushData(results); +const chargeResult = await Actor.charge({ eventName: 'result', count: results.length }); +await Actor.pushData(results.slice(0, chargeResult.chargedCount)); await Actor.exit(); ``` @@ -304,8 +304,8 @@ async def main(): {'url': 'https://example.com/3', 'title': 'Page 3'}, ] - await Actor.charge(event_name='result', count=len(results)) - await Actor.push_data(results) + charge_result = await Actor.charge(event_name='result', count=len(results)) + await Actor.push_data(results[:charge_result.charged_count]) await Actor.exit() ```