Generate sales battlecards from competitors' public pricing and feature pages: plan tiers and prices, a feature matrix, strengths and weaknesses, objection handling and win/loss tips, with a source URL for every fact. JSON, markdown and a one-pager per competitor.
All notable changes to this Actor are documented here. The format follows
Keep a Changelog , and the project tries to
adhere to Semantic Versioning — MAJOR.MINOR.PATCH.
2026-11-02: Pricing notes
README: the automatic Actor start fee ($0.00005 per GB of run memory) is mentioned under Cost.
2026-10-17 — Compliance update
Changed
Licence:LICENSE is now an all-rights-reserved notice (was MIT).
User-Agent: requests send
Mozilla/5.0 (compatible; AIDevsBot/1.0; +https://apify.com/entranced_gelato/saas-competitor-battlecards)
(was SaaSCompetitorBattlecardsBot/0.3 (...)). The user_agent override
still works; its description asks for an honest User-Agent.
robots.txt follows RFC 9309: the AIDevsBot group, else the old
SaaSCompetitorBattlecardsBot group, else *; longest match wins, */$
wildcards. 401/403 disallow the site; other 4xx allow everything; 429,
5xx, network errors and timeouts now disallow the site for the run (they
used to fail open). A custom User-Agent can only add restrictions.
Redirects are followed by the Actor itself, so a disallowed redirect
target is never requested. A disallowed page gets a company note, a
robots_disallowed risk flag, a mention in the status message and in
RUN_SUMMARY.robots_disallowed, and is never billed.
Discovery: a website you supply is the only site tried for that
company (no guessed <name>.com/.io/.ai/... domains); with only a name,
probing stops at the first confirmed domain. Each page is requested at
most once per run (trailing slashes, redirects, a second company on the
same site), and the extractors reuse the pages discovery fetched instead
of requesting them again. max_pages_per_company is now a hard cap.
Default and prefilled input: the demo pair is Apify (apify.com, our
company) vs Linear (linear.app) instead of Linear vs Asana, whose terms
forbid scraping. Defaults changed: our_company Apify, our_websitehttps://apify.com , competitors ["Linear"]; competitor_websites gained
the default {"Linear": "https://linear.app"} . sample_input.json, both
Store tasks (ClickUp left the market-scan task) and examples/ follow.
README: legal FAQ (vendor terms apply, data controller, lawful basis,
no absolute claims), AIDevsBot opt-out line, robots.txt and tdmReserved
FAQ entries, "Wrong charge?" (Issues tab, 14 days), data-protection line,
demo-pair note, published example runs as plain text with a "not
affiliated" line, new limitations.
Dev scripts send the Apify token in an Authorization: Bearer header
instead of ?token= (run_smoke.sh, configure_actor.py,
kill_switch.py, verify_listing.py); this also fixes kill_switch.py
listing running runs.
Added
Output fields tdmReserved and tdmPolicy on every page record
(companies[].pages[], sources[]): the page's TDMRep
tdm-reservation / tdm-policy header or <meta> tag (the tag wins).
Flags only; extraction still runs. Risk flag tdm_reserved.
RUN_SUMMARY.robots_disallowed and the robots_disallowed risk flag.
104 new tests (294 in total), all offline: RFC 9309 matching and status
handling, the URLs discovery and the prefilled input request (recorded
with httpx.MockTransport), TDMRep, README text, dev-script token
handling, limited-permission use. Three robots.txt tests changed with
the new rules (5xx and network errors now disallow; a custom User-Agent's
own group no longer lifts the * rules).
Removed
TERMS.md (an unfinished draft). Apify's Standard Actor Contract governs
use of Store Actors by default; the README points to it.
The unused DuckDuckGo search fallback (_ddg_search_top_hits).
[0.3] — 2026-10-10
Changed
Apify SDK 1.x → 4.x (apify>=4.1,<5). The 4.x SDK knows the run's
pay-per-event pricing, so the Actor can see the user's maximum charge.
requirements.lock.txt regenerated on Python 3.11.
Pricing statements corrected. The README and scripts said "$1 per
competitor analysis (Pay per Result)". Live pricing is pay per event: each
delivered battlecard is one default-dataset item (Apify's synthetic
apify-default-dataset-item event, titled "Battlecard generated"): $0.04
on the free plan, $0.036 Bronze, $0.032 Silver, $0.028 Gold and above.
Platform usage is included. The 0.2 notes below describe the old wording.
Only real battlecards reach the dataset. New per-company codes:
no_data (website resolved but no pricing and no features extracted) and
duplicate (same website as an earlier company, including our own). Both
are explained by a risk flag, kept in OUTPUT, and never billed.
Max total charge respected. Budget checked before any fetching and
before every push; pushing stops at the limit. The legacy event key
comparison_generated is kept but charged only if the pricing defines it.
OUTPUT.chargeable_event_count is the number of battlecards delivered.
Strengths/weaknesses feature samples are sorted, so the same input gives
the same text in every run (they depended on Python's hash seed).
Default User-Agent names the Store page (same bot token).
Domain parsing uses tldextract's bundled Public Suffix List instead of
downloading it from publicsuffix.org in every run (no third-party request;
tests are fully offline).
Block pages are never worked around. A site answering with HTTP 429,
or a 401/403/503 login wall, bot check or CAPTCHA, is remembered as
blocked: no retry and no further request to that host for the run. It is
noted on the company, flagged site_blocked, listed in
RUN_SUMMARY.blocked_sites and named in the status message. Missing data
is not billed.
README first screen rewritten: fictional sample battlecard, quick start,
cost table, FAQ (incl. "Is it legal?"), limitations, AIDevs links, and a
cross-link to the Competitor Website Analyzer.
actor.json: title "SaaS Battlecard Generator", version 0.3, memory
256/512/1024 MB, MCP_SERVERS category removed (this is not an MCP server).
Added
RUN_SUMMARY key-value record: delivered competitors, and every
competitor not delivered with the reason (candidates_only, no_data,
duplicate, charge_limit_reached). Terminal status message per run.
Dataset records gain competitor_website and pricing_headline.
.actor/dataset_schema.json (overview + detail views),
.actor/output_schema.json (dataset, OUTPUT, RUN_SUMMARY),
.actor/store-listing.json (Store copy, synced by hand at deploy).
CI on Python 3.11 (.github/workflows/ci.yml) with an offline schema
check (scripts/validate_actor.py).
70 new tests (whole-run tests with a fake Actor, billing helpers, blocked
sites, schema and listing contracts, README contract, determinism, offline
domain parsing); 190 in total.
[0.2] — 2026-05 (launch-prep)
Added
In-output disclaimer. Every battlecard now ships with a disclaimer
field rendered at the top of battlecard_markdown and one_pager_text.
Travels with every dataset record and every MCP tool call.
Per-competitor billing. Pay-per-Result now bills $1 per chargeable
competitor analysis. A market_scan with three resolved competitors
produces three dataset records ($3); a head_to_head produces one ($1).
Each record carries primary_competitor for auditing.
(Prices superseded: see the live prices in 0.3 above.)
Chargeable / non-chargeable distinction. Runs that end
candidates_only or failed no longer push a dataset record (no charge).
Users still see the result via the OUTPUT key-value record.
chargeable_event_count + paid_event_name fields on every output —
transparent billing receipt.
robots.txt enforcement + per-host rate limiting (0.5 s) for every
outbound HTTP request. Honors target-site preferences before fetching.
Top-level exception handler in main.py — every run produces a
structured record (success or failed). No silent crashes.
Competitor count cap (1–10) at both the JSON Schema and Pydantic
validation layers — bounds runtime + cost on adversarial inputs.
Locked dependency tree (requirements.lock.txt) — production builds
pin every transitive version, immune to upstream drift.
.actorignore — keeps tests, dev requirements, scripts, and internal
roadmap docs out of the published image.
Comprehensive smoke-test script (scripts/run_smoke.sh +
scripts/smoke_test.py) — validates the spec contract against the
deployed Actor in both chargeable and non-chargeable branches.
Plain-language Terms of Use (TERMS.md) inlined into the README so
visitors can read them without leaving the listing page.
(Removed on 2026-10-17: Apify's Standard Actor Contract applies.)
Test suite expanded to 120 unit tests covering input validation,
utils, discovery scoring, pricing extraction (incl. leaf-card detection
and free/paid disambiguation), feature extraction (incl. blocklist,
tagline, imperative-slogan filters), battlecard assembly, robots.txt,
rate limiting, and per-competitor billing.
Changed
README rewritten as a buyer-facing store listing — hero, who-it's-for,
sample output, pricing, FAQ, then dev info at the bottom.
Pricing extractor uses leaf-only card detection, eliminating the
bullet-bleed bug between adjacent plan cards.
Free-detection now scoped to the price-token window only — a casual
"Free" mention inside another plan's bullets no longer flips that plan.
Feature extractor prefers the dedicated features page over the
homepage; falls back to homepage only when no features page resolves.
Also drops footer/legal terms, marketing taglines (sentences with
conjugated verbs), and imperative slogans.
Fixed
ensure_scheme() is now case-insensitive on the scheme check
(HTTPS://x.com no longer gets double-prefixed).
detect_billing_period() recognizes spaced variants like $10 / month
(real pricing pages use these).
head_to_head strict semantics — flips to candidates_only if either
side fails to resolve, instead of mislabeling as partial.
Actor.charge call ordering — billing fields are now stamped before
push_data so the dataset record reflects the billing decision.
Security
Apify API token never echoed in script output (smoke test redacts it).
robots.txt 401/403 conservatively treated as "disallow all".
[0.1] — Initial release
First public version. Rule-based discovery, pricing extraction, feature
extraction, and battlecard assembly. Markdown + one-pager outputs derived
from canonical JSON.