SaaS Competitor Battlecards
Pricing
from $1,000.00 / 1,000 results
SaaS Competitor Battlecards
Generate sales-ready competitor battlecards from public pricing & feature pages. Cited sources, deterministic, MCP-ready. $1 per battlecard.
Pricing
from $1,000.00 / 1,000 results
Rating
0.0
(0)
Developer
AIDevs
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
3 days ago
Last modified
Categories
Share
Generate sales-ready competitor battlecards from public pricing and feature pages — in JSON, markdown, and one-pager formats.
This Actor finds the official website, pricing page, and features page for any SaaS company you name, extracts the structured pricing tiers and feature list, and assembles them into a battlecard your sales team can take into a deal. Every fact cites a source URL. Nothing is fabricated.
Disclaimer. Output is research material, not authoritative pricing or product information. Every fact is paired with a source URL and must be verified before use in customer-facing materials. Vendor names are used nominatively to identify products; no affiliation or endorsement is implied. Provided AS IS, without warranty. Full Terms of Use are summarized at the bottom of this README.
Who this is for
- Account executives prepping a competitive deal in 10 minutes instead of an hour.
- Solutions consultants / presales engineers building objection-handling notes ahead of a demo.
- Product marketing maintaining battlecards across a long competitor set.
- Founders running competitive analysis without hiring an SDR.
- AI agents and LLM tools — the Actor is exposed via Apify MCP, so Claude, Cursor, ChatGPT, and any MCP-aware client can call it as a structured tool.
What you get
For each run, a single dataset record with three views of the same battlecard:
- Structured JSON — the source of truth. 14 top-level fields including pricing tiers, feature comparison matrix, positioning, strengths/weaknesses, objection handling, win/loss guidance, risk flags, and citation list.
- Markdown summary — copy-paste into Notion, Confluence, or a Slack message.
- One-pager text — terminal-style brief sized for a single PDF page.
Sample one-pager
BATTLECARD — Linear vs Asana========================================================================Mode: head_to_head | Status: ok | Confidence: 86/100PRICING AT A GLANCE------------------------------------------------------------------------Linear: USD 8.0/monthly | free tier ✓Asana: USD 10.99/monthly | free tier ✓ | contact-sales ✓CAPABILITIES SIGNAL (top promoted, by company)------------------------------------------------------------------------Linear: Issue tracking, Cycles, Roadmaps, Triage, Linear AsksAsana: Tasks, Projects, Workflows, Goals, ReportingWHY WE WIN / WHY WE LOSE------------------------------------------------------------------------Asana:+ Cheaper entry tier than ours- No transparent enterprise pricing — slow procurementTOP OBJECTIONS------------------------------------------------------------------------Objection: "Asana has a free tier — why pay for yours?"Response : Acknowledge the free tier; reframe to TCO and outcomes.
How to use it
From the Apify console (no code)
- Click Try for free (or Start, if you've used it before).
- In the Input panel, enter:
- Mode:
head_to_head(your product vs one competitor) ormarket_scan(multiple competitors). - Our company: your company name (required for head-to-head).
- Competitors: the company names you want to research (1–10).
- Competitor websites (optional but recommended): a name → URL map. Skips discovery and goes straight to that vendor's site, which gives much cleaner extraction.
- Mode:
- Click Save & Start. A run typically finishes in 30–90 seconds.
- Open the run's Output tab to see the battlecard, or the Storage → Default dataset tab for the structured JSON.
From an MCP-enabled LLM (Claude Desktop, Cursor, etc.)
If your MCP client is configured with the Apify MCP server, this Actor is exposed automatically as a tool. The Actor's input schema becomes the tool's argument schema, and the canonical JSON output becomes the tool's result. Just ask: "Generate a battlecard comparing Linear and Asana" and the LLM will call this Actor with the right arguments.
Programmatically (Apify API)
curl -X POST "https://api.apify.com/v2/acts/<ACTOR_ID>/runs?token=<TOKEN>" \-H "Content-Type: application/json" \-d @sample_input.json
Input
| Field | Type | Default | Description |
|---|---|---|---|
mode | enum | head_to_head | head_to_head (you vs one competitor) or market_scan (multiple competitors). |
our_company | string | — | Your company name. Required for head_to_head. |
our_website | string | — | Optional canonical URL for your company. Skips discovery. |
competitors | array of string | [] | Competitor names. Min 1, max 10. For head_to_head, only the first is used. |
competitor_websites | object | — | Optional {name: url} map. Skips discovery for those competitors. |
confidence_threshold | int | 60 | If overall confidence falls below this (0–100), the Actor returns ranked candidates instead of guessing. |
max_pages_per_company | int | 6 | Soft cap on HTTP fetches per company. Bumps cost/runtime. |
currency_hint | string | USD | Helps pricing normalization when a page exposes multiple currencies. |
locale_hint | string | en-US | Helps pick the right localized variant of a pricing page. |
user_agent | string | (polite default) | Override the default identifying User-Agent. |
request_timeout_seconds | int | 20 | Per-request HTTP timeout. |
Example input — head-to-head
{"mode": "head_to_head","our_company": "Linear","our_website": "https://linear.app","competitors": ["Asana"],"competitor_websites": { "Asana": "https://asana.com" },"confidence_threshold": 60}
Example input — market scan
{"mode": "market_scan","competitors": ["Asana", "Notion", "Monday"],"competitor_websites": {"Asana": "https://asana.com","Notion": "https://www.notion.so","Monday": "https://monday.com"},"confidence_threshold": 60}
Output
The 14 top-level fields:
| Field | Description |
|---|---|
request_context | Echo of the request: mode, companies, threshold, timestamps. |
companies | Per-company resolution: name, resolved_website, confidence, candidates, discovered pages. |
confidence | Per-company website/pricing/feature scores plus a composite, and overall vs threshold. |
sources | Flat list of every URL that was fetched, with category (pricing/features/homepage). |
pricing_summary | Per-company plan tiers, prices, billing periods, free/custom flags, lowest paid price. |
feature_comparison | Normalized matrix of features × companies with yes/unknown coverage flags. |
positioning_summary | Short narrative positioning per company. Inferred, clearly labeled. |
strengths_and_weaknesses | Per-competitor strengths and weaknesses with citations. |
objection_handling | Common objections and suggested responses. |
win_loss_guidance | When to lean in, when to walk away. |
risk_flags | Data quality warnings: low confidence, no pricing page, custom-only pricing, etc. |
battlecard_markdown | Readable markdown summary derived from the JSON. |
one_pager_text | Presentation-style brief derived from the JSON. |
output_status | ok / partial / candidates_only / failed plus a per-company breakdown. |
Trust contract — what this Actor will and will not do
- Will cite the source URL for every extracted fact.
- Will clearly distinguish extracted facts from inferred insights.
- Will return ranked candidates instead of a fabricated answer when discovery confidence is below threshold (
output_status.code = "candidates_only"). - Will preserve enterprise/contact-sales pricing as
is_custom: truewith the raw text. - Will not invent a price for a vendor whose pricing page doesn't publish one.
- Will not spoof user agents or bypass
robots.txt.
Pricing
This Actor uses Pay per Result — $1.00 USD per chargeable competitor analysis.
- Head-to-head run: $1. Always exactly one billable competitor.
- Market-scan with N resolved competitors: $N. A 3-competitor scan costs $3, a 5-competitor scan costs $5. Each chargeable competitor gets its own dataset record so you can audit billing per-row.
candidates_onlycompetitors: free. If the Actor couldn't confirm an official website for a competitor, that competitor isn't billed. (You'll see the ranked candidates inOUTPUTso you can retry with the right URL incompetitor_websites.)failedruns: free. Input validation errors and unexpected pipeline failures don't push a record.
How billing maps to records. Each chargeable competitor analysis = one dataset record = $1. Open any run → Storage → Default dataset → count the records → that's the dollar cost.
Worked examples:
| Input | Resolved | Records | Cost |
|---|---|---|---|
head_to_head Linear vs Asana | Asana ✓ | 1 | $1 |
head_to_head Linear vs UnknownCo | UnknownCo ✗ | 0 | $0 |
market_scan [Asana, Notion, Monday] | All 3 ✓ | 3 | $3 |
market_scan [Asana, Notion, FakeCo] | 2 of 3 ✓ | 2 | $2 |
market_scan [FakeCo1, FakeCo2] | 0 of 2 ✓ | 0 | $0 |
The chargeable_event_count field in OUTPUT (and in every dataset record) tells you the per-run total explicitly.
How it works (short version)
- Discovery. For each company, resolve the canonical website. If you provide it via
competitor_websites, the Actor uses it directly. Otherwise it tries deterministic TLD guesses (.com,.io,.ai,.app, …) and scores each candidate against the company name's appearance in<title>,og:site_name, and meta description. - Page discovery. From the homepage, find links matching
/pricing,/plans,/features,/product, etc. Verify each candidate URL exists. - Extraction. Rule-based HTML parsing identifies plan-card-shaped DOM regions, extracts plan name, price token, billing period, free/custom flags, and feature bullets. Feature labels are mined from
<h2>/<h3>headings and<li>bullets on the features page, then normalized through a tagline/footer/imperative-slogan filter. - Assembly. The canonical JSON is built first, then
battlecard_markdownandone_pager_textare derived from it. Markdown and one-pager are NEVER the source of truth — the JSON is.
The Actor is deterministic and explainable. No LLM API is called at runtime. Same inputs → same outputs.
FAQ
Q. Will this work for any SaaS company?
A. It works best for SaaS products with a first-party domain (e.g. linear.app, notion.so) and a publicly-published pricing page. It struggles with: (a) sub-products under bigger brands like aws.amazon.com/q/ or github.com/features/copilot — pass these via competitor_websites and pricing will usually still work; (b) pricing pages rendered entirely client-side in JavaScript — these will return empty plans with a no_pricing_page risk flag.
Q. Why does my run say candidates_only?
A. Because the Actor couldn't confirm an official website for at least one company you named with high enough confidence. Look at companies[*].candidates for the ranked alternatives, pick the right one, and re-run with that URL in competitor_websites. This is the trust contract working as designed — better an honest "I'm not sure" than a fabricated battlecard.
Q. Is the output current? A. The Actor extracts what's on the vendor's pages right now, at run time. It doesn't cache. If a vendor changed their pricing yesterday, your battlecard reflects yesterday's change. But it's snapshot-only — the Actor doesn't track changes over time (yet).
Q. Can I trust the strengths/weaknesses analysis? A. Treat it as a starting draft, not a finished position. The strengths/weaknesses are derived deterministically from extracted facts (e.g. "no free tier", "lower entry price than ours"), but they don't capture qualitative things like product polish, customer experience, or roadmap. They're meant to seed your prep, not finish it.
Q. Does this use my data or my prospects' data? A. No. The Actor only fetches public marketing pages of the companies you name. It doesn't accept, process, or transmit any personally identifiable information. All output stays in your Apify account's storage.
Q. Is this legal? Does it violate vendor ToS?
A. The Actor only fetches publicly available marketing pages, honors robots.txt, uses an identifying User-Agent (no spoofing), and rate-limits per host. That's the same posture used by Google, Bing, G2, Capterra, and every other comparison/review site. It's your responsibility to comply with the terms of service of any specific target site.
Q. Can I run this through an LLM agent? A. Yes — that's a first-class use case. The Actor is registered with Apify MCP, so any MCP-aware client (Claude Desktop, Cursor, ChatGPT with MCP, …) can call it as a tool. Pass natural-language input, get back the structured JSON.
Limitations
- JS-only pricing pages. Pricing rendered entirely client-side will yield empty
plansand ano_pricing_pagerisk flag. v1 is HTML-only by design. An optional LLM-enrichment pass is on the v2 roadmap. - Sub-products of big brands. Companies like AWS, Google Cloud, GitHub features, etc. need an explicit
competitor_websitesURL to work well — discovery on the parent domain is too noisy. - Marketing prose vs feature labels. Some vendors put taglines and slogans in
<h2>headings on their features page. The normalizer filters most of these (taglines with conjugated verbs, imperative slogans, footer/legal terms) but precision is conservative — a few legitimate features may be filtered out. - No historical / change tracking. Each run is a snapshot. Persistent change detection is on the v2 roadmap.
Changelog
0.2 (launch-prep)
- Added in-output
disclaimerfield — travels with every battlecard. - Added
chargeable_event_countfield for transparent billing. - Added robots.txt enforcement and per-host rate limiting.
- Added top-level exception handler — every run produces a structured record.
- Capped
competitorsto 1–10 to bound runtime/cost. - Locked dependency tree (
requirements.lock.txt). - Tightened pricing extraction (no bullet bleed-over between plan cards, free/paid disambiguation).
- Tightened feature extraction (drops footer/legal terms, taglines, imperative slogans).
0.1
- Initial release.
For developers
The remaining sections are for engineers who want to read or modify the code. If you're using the Actor through the console or MCP, you can stop here.
Architecture
.├── .actor/│ ├── actor.json│ └── input_schema.json├── OUTPUT_SHAPE.json # Reference JSON Schema for the canonical output├── Dockerfile├── requirements.txt # Loose ranges (intent)├── requirements.lock.txt # Exact pins (production builds)├── requirements-dev.txt # Dev/test extras (pytest)├── main.py # Apify Actor entrypoint (async)├── sample_input.json├── examples/│ └── copilot_vs_claude_vs_q.json├── README.md├── TERMS.md└── src/├── __init__.py├── models.py # Pydantic models incl. final BattlecardOutput├── utils.py # HTTP, URL, money/period parsing helpers├── robots.py # robots.txt cache + per-host policy├── discovery.py # Phase 2: website + pricing/features pages├── extract_pricing.py # Phase 3a: rule-based pricing extraction├── extract_features.py # Phase 3b: rule-based feature extraction└── battlecard.py # Phase 4: assemble canonical JSON + MD + 1-pager
Each extraction module is isolated and testable on its own — they only take HTTP results + already-resolved URLs as inputs. There is no hard dependence on any LLM API. To enable model-assisted enrichment later you would add new functions inside extract_pricing.py / extract_features.py (or a new enrichment.py) and call them after the rule-based pass — the canonical JSON shape doesn't change.
Confidence model
For each company we compute three sub-scores (0–100):
- website — derived during discovery from domain match,
<title>,og:site_name, and meta description. - pricing — set by
extract_pricingbased on whether structured plans were detected with prices and billing periods. - features — set by
extract_featuresbased on the count and quality of normalized feature phrases.
The composite is 0.45 × website + 0.35 × pricing + 0.20 × features. The overall run confidence is the mean across companies. If overall is below confidence_threshold, the Actor still emits the JSON but flips output_status.code to candidates_only or partial and adds a low_confidence_resolution risk flag.
Run locally
pip install -r requirements.lock.txtAPIFY_LOCAL_STORAGE_DIR=./storage python main.py
To use a custom input locally, drop your input JSON into ./storage/key_value_stores/default/INPUT.json (Apify's local layout).
Tests
pip install -r requirements-dev.txtpytest
107 unit tests, no network, ~150 ms total. Coverage:
test_models.py— Pydantic input validation: empty competitors, head-to-head requiresour_company, dedupe, threshold/timeout bounds, the 14-field output shape, competitor cap (1–10), disclaimer field.test_utils.py— money/billing parsing, URL normalization (case-insensitive scheme), domain extraction,looks_like_freeprecision,looks_like_custom_pricing.test_extract_pricing.py— four-tier Linear-shaped page, leaf-card detection (no bullet bleed-over), Free/Basic disambiguation, Enterprise contact-sales detection, fallback heading scan.test_extract_features.py— heading + bullet mining, blocklist (Legal/Privacy/Press), tagline filter ("X is Y"), imperative-slogan filter, hyphenated terms preserved, features page preferred over homepage.test_battlecard.py— all top-level fields present, head-to-head goes tocandidates_onlywhen one side fails, market-scan partial vs candidates-only, derived fields (lowest paid price, feature totals, risk flags), markdown + one-pager rendering, disclaimer appears in markdown.test_robots.py— robots.txt parsing + caching, allow/deny logic, fail-open on transient errors, per-host rate-limiting.test_smoke.py— end-to-end: synthetic homepage / pricing / features pages → extractors → assembly → JSON-serializable battlecard with all 14 fields andoutput_status.code == "ok".
Extending
- Better discovery. Enable the DuckDuckGo HTML fallback in
discovery.py(_ddg_search_top_hits) when domain-guessing fails. - Locale-aware pricing. Add per-locale pricing-page heuristics to
discovery._conventional_path_guesses. - LLM enrichment. Add an enrichment pass after
_extract_for_oneinextract_pricing.py/extract_features.pythat takes the rule-based result + the raw HTML and refines it. Keep the canonical JSON shape. - Change detection. Persist past runs in a key-value store and compare pricing snapshots over time — naturally extends
risk_flagswith apricing_changedcode.
Terms of Use (summary)
By running this Actor or consuming its output, you agree to the following.
AS IS, no warranty. The Actor and its output are provided AS IS and AS AVAILABLE. The operator does not warrant accuracy, currency, completeness, fitness for any purpose, or non-infringement. The Actor's output_status, risk_flags, and confidence scores indicate the system's own honest uncertainty — treat them as such.
Limitation of liability. To the maximum extent permitted by applicable law, the operator's aggregate liability arising out of or related to the Actor or its output shall not exceed the greater of the amount you paid for the Actor in the twelve months preceding the event giving rise to the claim, or USD 100. The operator shall not be liable for indirect, incidental, consequential, special, exemplary, or punitive damages.
Vendor names. Vendor names appearing in inputs and outputs are used nominatively to identify the products being researched. No affiliation, endorsement, or sponsorship by any named vendor is claimed or implied. The Actor does not reproduce vendor logos.
Your responsibilities. You are solely responsible for compliance with the terms of service of any target site you ask the Actor to fetch, for verifying every extracted fact against the cited source URL before using it in customer-facing material, and for any commercial outcome resulting from your use of the output.
Prohibited uses. Do not use the Actor to generate misleading, defamatory, or factually false statements about any vendor; access content behind authentication or paywalls; or process personally identifiable information.
Data handling. The Actor processes only publicly available marketing pages. It does not require, accept, or process personally identifiable information. All output is stored exclusively in your Apify account's run dataset and key-value store. The operator does not maintain a separate copy.
For questions, feature requests, or to report issues, contact the Actor maintainer through the Apify console.