Pay-per-event monetization
With the pay-per-event pricing model, 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.
Configure monetization
To use the pay-per-event pricing model and define the pricing, monetize your Actor in Apify Console.
Charge for events
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.
import asyncio
from apify import Actor
async def main() -> None:
async with Actor:
# Charge for a single occurrence of an event
await Actor.charge(event_name='init')
# Prepare some mock results
result = [
{'word': 'Lorem'},
{'word': 'Ipsum'},
{'word': 'Dolor'},
{'word': 'Sit'},
{'word': 'Amet'},
]
# Shortcut for charging for each pushed dataset item
await Actor.push_data(result, charged_event_name='result-item')
# Or you can charge for a given number of events manually
await Actor.charge(
event_name='result-item',
count=len(result),
)
if __name__ == '__main__':
asyncio.run(main())
For details on how to maximize your profits, see also Best practices.
Prefer per-unit charging
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:
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())
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.
Monitor charging
For both custom and synthetic events, every Actor.charge call returns a ChargeResult. Inspect its fields to learn how much was charged.
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:
import asyncio
from apify import Actor
async def main() -> None:
async with Actor:
charging_manager = Actor.get_charging_manager()
# Check the total budget for this run
max_charge = charging_manager.get_max_total_charge_usd()
Actor.log.info(f'Max total charge: ${max_charge}')
# Check how many events can still be charged
remaining = charging_manager.calculate_max_event_charge_count_within_limit(
'result-scraped',
)
Actor.log.info(f'Remaining chargeable events: {remaining}')
# Get the total amount charged so far
total_charged = charging_manager.calculate_total_charged_amount()
Actor.log.info(f'Total charged so far: ${total_charged}')
# Check all event types and their remaining counts
chargeable = charging_manager.compute_chargeable()
Actor.log.info(f'Chargeable events: {chargeable}')
# Check if a specific event type has reached its limit
if charging_manager.is_event_charge_limit_reached('result-scraped'):
Actor.log.info('Budget exhausted for result-scraped events')
if __name__ == '__main__':
asyncio.run(main())
Handle 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.
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.
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:
# Do some expensive work (e.g. scraping, API calls)
result = {'url': url, 'data': f'Scraped data from {url}'}
# 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'
)
if charge_result.event_charge_limit_reached:
Actor.log.info('Charge limit reached, stopping the Actor')
break
if __name__ == '__main__':
asyncio.run(main())
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.
Test monetization locally
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:
ACTOR_TEST_PAY_PER_EVENT=true python -m youractor
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.
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.
Transition 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 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.
import asyncio
from apify import Actor
async def main() -> None:
async with Actor:
# Check the dataset because there might already be items
# if the run migrated or was restarted
default_dataset = await Actor.open_dataset()
metadata = await default_dataset.get_metadata()
charged_items = metadata.item_count
if Actor.get_charging_manager().get_pricing_info().is_pay_per_event:
await Actor.push_data({'hello': 'world'}, charged_event_name='dataset-item')
elif charged_items < (Actor.configuration.max_paid_dataset_items or 0):
await Actor.push_data({'hello': 'world'})
charged_items += 1
if __name__ == '__main__':
asyncio.run(main())