Skip to content

feat(charging): Support the idempotency_key param in Actor.charge() - #1123

Open
Mantisus wants to merge 8 commits into
apify:masterfrom
Mantisus:charge-idempotency-key
Open

feat(charging): Support the idempotency_key param in Actor.charge()#1123
Mantisus wants to merge 8 commits into
apify:masterfrom
Mantisus:charge-idempotency-key

Conversation

@Mantisus

@Mantisus Mantisus commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator

Adds support for the idempotency_key parameter in Actor.charge().

Closes: #1122

@Mantisus Mantisus changed the title feat(charging): Allow passing an idempotency key to charge() feat(charging): Support the idempotency_key param in Actor.charge() Sep 8, 2026
@Mantisus Mantisus self-assigned this Sep 8, 2026
@Mantisus
Mantisus requested a review from vdusek September 8, 2026 21:28
@vdusek
vdusek requested a review from szaganek as a code owner September 11, 2026 11:04
Comment thread src/apify/_charging.py Outdated
Comment on lines +511 to +510
# Only remember a key that stands for a charge the platform actually received. Off the platform there
# is no request at all and the registry is the only thing providing deduplication.
if idempotency_key is not None and (not self._is_at_home or charge_sent):
# Remember the key for every charge that was counted, including events the API never receives, such as
# synthetic and tier-priced ones - those are counted locally and a repeat would count them twice.
if idempotency_key is not None:

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With this update, the idempotency_key will be saved even for events that do not exist.

@Mantisus

Copy link
Copy Markdown
Collaborator Author

@barjin, Please take a look at the implementation.

In the TS SDK, the idempotency_key doesn't affect the ChargingManager. This will cause desynchronization in cases where the API rejects a request with a duplicate idempotency_key.

@barjin barjin left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @Mantisus !

Good catch, we need this in JS SDK as well, otherwise the deduplicated charges will eventually desync the running total count. I opened an issue here.

Otherwise, I left a few ideas regarding the implementation ⬇️ Thanks again!

Comment thread src/apify/_charging.py
Comment on lines +446 to +448
# A repeat is resolved from this registry rather than left to the platform, whose own idempotency
# record expires after a few minutes: a late repeat would charge a second time, and counting it here
# would inflate the charging state and make the run hit `max_total_charge_usd` early.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is surprising, but true (the API implementation has a 3-minute expiration window on the idempotency records (source)).

I think we should follow the Platform behaviour here and allow for this idempotency "cooldown" period. This should also allow us to remove the old local idempotency records.

Comment thread src/apify/_charging.py
Comment on lines +223 to +224
Keys are remembered for the lifetime of the Actor process. A key belongs to a single event, so
reusing one for a different event raises `ValueError`, as does passing a blank key.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A key belongs to a single event, so reusing one for a different event raises ValueError

This does sound logical, but it's not how the API works. Should we implement this logic here?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support the idempotency_key param in Actor.charge()

4 participants