Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
85 changes: 49 additions & 36 deletions docs/02_concepts/08_pay_per_event.mdx
Original file line number Diff line number Diff line change
@@ -1,78 +1,91 @@
---
id: pay-per-event
title: Pay-per-event Monetization
description: Monetize your Actors using the pay-per-event pricing model
title: Pay-per-event monetization
description: Monetize your Actors using the pay-per-event pricing model.
---

import ActorChargeSource from '!!raw-loader!./code/actor_charge.ts';
import ConditionalActorChargeSource from '!!raw-loader!./code/conditional_actor_charge.ts';
import ChargeLimitCheckSource from '!!raw-loader!./code/charge_limit_check.ts';
import PerUnitChargingSource from '!!raw-loader!./code/per_unit_charging.ts';
import ChargingManagerSource from '!!raw-loader!./code/charging_manager.ts';
import ApiLink from '@site/src/components/ApiLink';
import CodeBlock from '@theme/CodeBlock';

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, you first need to [set it up](https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event) for your Actor in the Apify console. After that, you're free to 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 <ApiLink to="class/Actor#charge">`Actor.charge`</ApiLink> calls to your code and start monetizing!
To charge for events, use the <ApiLink to="class/Actor#charge">`Actor.charge`</ApiLink> method. It records that your Actor performed a billable activity, so the Apify platform charges the user's account for it.

<CodeBlock language="typescript">{ActorChargeSource}</CodeBlock>

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 access call <ApiLink to="class/Actor#getChargingManager">`Actor.getChargingManager()`</ApiLink> to access the <ApiLink to="class/ChargingManager">`ChargingManager`</ApiLink>, 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 over batching

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.pushData` 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.
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:

To avoid this, you should check the `eventChargeLimitReached` field in the result returned by <ApiLink to="class/Actor#charge">`Actor.charge`</ApiLink> or `Actor.pushData` and stop your Actor when the limit is reached. You can also use the `chargeableWithinLimit` field from the result to plan ahead — it tells you how many events of each type can still be charged within the remaining budget.
<CodeBlock language="typescript">{PerUnitChargingSource}</CodeBlock>

<CodeBlock language="typescript">{ChargeLimitCheckSource}</CodeBlock>
If you use the `count` parameter, always check the returned `chargedCount`. It tells you how many events were charged, which may be less than what you requested.

Alternatively, you can periodically check the remaining budget via <ApiLink to="class/Actor#getChargingManager">`Actor.getChargingManager()`</ApiLink> 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.
### Monitor charging

:::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.
:::
For custom events, every `Actor.charge` call returns <ApiLink to="interface/ChargeResult">`ChargeResult`</ApiLink>. Inspect its fields to learn how much was charged.

## Best practices for charging
Instead of inspecting every `ChargeResult`, you can also use the <ApiLink to="class/ChargingManager">`ChargingManager`</ApiLink>. It provides methods for querying the remaining budget, total charged amount, and per-event charge counts.

### Prefer per-unit charging over batching
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.

When your work can be split into individual units (e.g. scraping one page, calling one API endpoint), prefer issuing one `Actor.charge()` call per unit rather than batching multiple events into a single call via the `count` parameter. This gives you finer control over budget consumption:
To access the <ApiLink to="class/ChargingManager">`ChargingManager`</ApiLink>, use the <ApiLink to="class/Actor#getChargingManager">`Actor.getChargingManager()`</ApiLink> method:

<CodeBlock language="typescript">{PerUnitChargingSource}</CodeBlock>
<CodeBlock language="typescript">{ChargingManagerSource}</CodeBlock>

If you do use the `count` parameter, always check the returned `chargedCount` — it tells you how many events were actually charged, which may be less than what you requested.
### Handle the charge limit

### Always check the charge limit
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.

Both `Actor.charge` and `Actor.pushData` return a `ChargeResult` object. Always inspect `eventChargeLimitReached` or `chargedCount` and stop doing work when the budget is exhausted. Without this check, your Actor will continue consuming platform resources without producing value for the user.
The user spending limit for the run is available in your Actor code as the `ACTOR_MAX_TOTAL_CHARGE_USD` environment variable, and <ApiLink to="interface/ChargeResult">`ChargeResult`</ApiLink> already accounts for it. To control the limit, inspect the fields of `ChargeResult`:

## Transitioning from a different pricing model
- `eventChargeLimitReached`: Checks if the user's spending limits has been reached and doesn't allow for another charge of the event.
- `chargeableWithinLimit`: Indicates how many events of each type can still be charged within the remaining budget.
- `chargedCount`: Indicates how many events were billed by the call.

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 will need support both pricing models during the transition period enforced by the Apify platform. Arguably 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 <ApiLink to="class/ChargingManager#getPricingInfo">`ChargingManager.getPricingInfo`</ApiLink> method which returns information about the current pricing model.
<CodeBlock language="typescript">{ChargeLimitCheckSource}</CodeBlock>

<CodeBlock language="typescript">{ConditionalActorChargeSource}</CodeBlock>
When the charge limit is reached, <ApiLink to="class/Actor#charge">`Actor.charge`</ApiLink> stops charging and <ApiLink to="class/Actor#pushData">`Actor.pushData`</ApiLink> 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).

## Local development
## 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:

```shell
ACTOR_TEST_PAY_PER_EVENT=true npm start
```

If you also wish to see a log of all the events charged throughout the run, you also need to pass the `ACTOR_USE_CHARGING_LOG_DATASET` environment variable. Your charging dataset will then be available 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.
### View the log

Because pricing configuration is stored by the Apify platform, all events will have a default price of $1.
To inspect the results of your tests:

1. In addition to the `ACTOR_TEST_PAY_PER_EVENT` environment variable, pass `ACTOR_USE_CHARGING_LOG_DATASET`:

```shell
ACTOR_TEST_PAY_PER_EVENT=true ACTOR_USE_CHARGING_LOG_DATASET=true npm start
```

1. Open the `charging_log` dataset. By default, it's stored in the `storage/datasets/charging_log/` directory.

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.

Note that this log isn't available when running the Actor in production on the Apify platform.

## Transitioning from a different pricing model

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 will need support both pricing models during the transition period enforced by the Apify platform. Arguably 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 <ApiLink to="class/ChargingManager#getPricingInfo">`ChargingManager.getPricingInfo`</ApiLink> method which returns information about the current pricing model.

<CodeBlock language="typescript">{ConditionalActorChargeSource}</CodeBlock>
17 changes: 17 additions & 0 deletions docs/02_concepts/code/charging_manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Actor } from 'apify';

await Actor.init();

const chargingManager = Actor.getChargingManager();

// Check the total budget for this run
const maxCharge = chargingManager.getMaxTotalChargeUsd();
console.log(`Max charge: ${maxCharge}`);

// Check how many events can still be charged before reaching the limit
const remainingCharge = chargingManager.calculateMaxEventChargeCountWithinLimit(
'result-item',
);
console.log(`Remaining number of events: ${remainingCharge}`);

await Actor.exit();
Loading
Loading