Skip to content

apify actors info <actor> crashes with toFixed error on paid/monetized Store Actors #1171

@ttokttokttok

Description

@ttokttokttok

Summary

apify actors info <actorId> (the default, human-readable output) throws and exits
1 for Store Actors that use tiered pay-per-event pricing, while working fine for
free Actors and Actors with simpler pricing. The crash is in the pricing renderer,
which reads a flat eventPriceUsd on each charge event and calls .toFixed() on
it — but tiered-priced Actors don't have that field (their prices live under
eventTieredPricingUsd.<TIER>.tieredEventPriceUsd), so it's undefined. The
--input, --readme, and --json flags all work on the same Actors because they
bypass the pricing renderer.

Environment

  • apify-cli 1.6.1 (latest on npm at time of filing)
  • Reproduced on Node 24.11.1 / Windows x64 and Node 22.22.3 / Linux x64

Steps to reproduce

# crash — tiered pay-per-event Store Actors
npx apify-cli actors info lukaskrivka/google-maps-with-contact-details
npx apify-cli actors info compass/google-maps-reviews-scraper
npx apify-cli actors info apify/google-search-scraper

# works — free Actors
npx apify-cli actors info apify/hello-world
npx apify-cli actors info apify/web-scraper

# works — same paid Actor, pricing renderer bypassed
npx apify-cli actors info lukaskrivka/google-maps-with-contact-details --input
npx apify-cli actors info lukaskrivka/google-maps-with-contact-details --json

Expected

The info card prints for tiered pay-per-event Actors (exit 0), showing the tiered
prices (or skipping the price gracefully when the flat field is absent).

Actual

Error: Cannot read properties of undefined (reading 'toFixed')

Exit code 1, no output.

Root cause (verified)

The affected Actors use pricingModel: "PAY_PER_EVENT" with tiered per-event
pricing. Their charge events have no flat eventPriceUsd; prices are nested under
eventTieredPricingUsd. Verified via apify actors info lukaskrivka/google-maps-with-contact-details --json:

"pricingModel": "PAY_PER_EVENT",
"pricingPerEvent": {
  "actorChargeEvents": {
    "place-scraped": {
      "eventTitle": "Scraped place",
      "eventTieredPricingUsd": {
        "FREE":   { "tieredEventPriceUsd": 0.005 },
        "BRONZE": { "tieredEventPriceUsd": 0.004 },
        "SILVER": { "tieredEventPriceUsd": 0.003 }
        // ... GOLD / PLATINUM / DIAMOND
      },
      "isPrimaryEvent": true
      // NOTE: there is no "eventPriceUsd" key
    }
  }
}

The pricing renderer (minified in the published 1.6.1 package at
dist/_register-DeIDNArb.js) reads the flat field unguarded:

const events = Object.values(actor.pricingPerEvent?.actorChargeEvents ?? {});
for (const ev of events)
  table.pushRow({ [titleCol]: ev.eventTitle, [priceCol]: bold(`$${ev.eventPriceUsd.toFixed(2)}`) });
//                                                            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
//  ev.eventPriceUsd is undefined for tiered-priced events → "Cannot read properties of undefined (reading 'toFixed')"

So the renderer only handles flat per-event pricing and throws on tiered pricing.
Free Actors don't enter this branch, which is why they don't crash.

Impact

actors info is the primary command for inspecting an Actor, and it fails on
exactly the commercial, tiered-priced Store Actors that real workflows depend on
(e.g. the Google Maps scrapers). Any script or agent that shells out to it gets no
usable output and a non-zero exit.

Workaround

Use --input (input schema), --readme, or --json — all skip the broken pricing
renderer.

Suggested fix

Handle tiered pricing and guard the flat path. For each charge event, prefer
eventTieredPricingUsd when present (render the per-tier prices, or the user's
plan tier / the FREE tier), and fall back to eventPriceUsd only when it is a
number:

const flat = ev.eventPriceUsd;
const tiered = ev.eventTieredPricingUsd;
const price =
  typeof flat === "number"
    ? `$${flat.toFixed(2)}`
    : tiered
      ? `$${(tiered.FREE?.tieredEventPriceUsd ?? 0).toFixed(2)}+ (tiered)`
      : "N/A";

The adjacent PRICE_PER_DATASET_ITEM branch (pricePerUnitUsd * 1e3).toFixed(2))
should get the same typeof === "number" guard.

Metadata

Metadata

Assignees

Labels

t-dxIssues owned by the DX team.

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions