SaaS Competitor Battlecards avatar

SaaS Competitor Battlecards

Pricing

from $1,000.00 / 1,000 results

Go to Apify Store
SaaS Competitor Battlecards

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

AIDevs

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

3 days ago

Last modified

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:

  1. 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.
  2. Markdown summary — copy-paste into Notion, Confluence, or a Slack message.
  3. 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/100
PRICING 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 Asks
Asana: Tasks, Projects, Workflows, Goals, Reporting
WHY WE WIN / WHY WE LOSE
------------------------------------------------------------------------
Asana:
+ Cheaper entry tier than ours
- No transparent enterprise pricing — slow procurement
TOP 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)

  1. Click Try for free (or Start, if you've used it before).
  2. In the Input panel, enter:
    • Mode: head_to_head (your product vs one competitor) or market_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.
  3. Click Save & Start. A run typically finishes in 30–90 seconds.
  4. 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

FieldTypeDefaultDescription
modeenumhead_to_headhead_to_head (you vs one competitor) or market_scan (multiple competitors).
our_companystringYour company name. Required for head_to_head.
our_websitestringOptional canonical URL for your company. Skips discovery.
competitorsarray of string[]Competitor names. Min 1, max 10. For head_to_head, only the first is used.
competitor_websitesobjectOptional {name: url} map. Skips discovery for those competitors.
confidence_thresholdint60If overall confidence falls below this (0–100), the Actor returns ranked candidates instead of guessing.
max_pages_per_companyint6Soft cap on HTTP fetches per company. Bumps cost/runtime.
currency_hintstringUSDHelps pricing normalization when a page exposes multiple currencies.
locale_hintstringen-USHelps pick the right localized variant of a pricing page.
user_agentstring(polite default)Override the default identifying User-Agent.
request_timeout_secondsint20Per-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:

FieldDescription
request_contextEcho of the request: mode, companies, threshold, timestamps.
companiesPer-company resolution: name, resolved_website, confidence, candidates, discovered pages.
confidencePer-company website/pricing/feature scores plus a composite, and overall vs threshold.
sourcesFlat list of every URL that was fetched, with category (pricing/features/homepage).
pricing_summaryPer-company plan tiers, prices, billing periods, free/custom flags, lowest paid price.
feature_comparisonNormalized matrix of features × companies with yes/unknown coverage flags.
positioning_summaryShort narrative positioning per company. Inferred, clearly labeled.
strengths_and_weaknessesPer-competitor strengths and weaknesses with citations.
objection_handlingCommon objections and suggested responses.
win_loss_guidanceWhen to lean in, when to walk away.
risk_flagsData quality warnings: low confidence, no pricing page, custom-only pricing, etc.
battlecard_markdownReadable markdown summary derived from the JSON.
one_pager_textPresentation-style brief derived from the JSON.
output_statusok / 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: true with 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_only competitors: free. If the Actor couldn't confirm an official website for a competitor, that competitor isn't billed. (You'll see the ranked candidates in OUTPUT so you can retry with the right URL in competitor_websites.)
  • failed runs: 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:

InputResolvedRecordsCost
head_to_head Linear vs AsanaAsana ✓1$1
head_to_head Linear vs UnknownCoUnknownCo ✗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)

  1. 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.
  2. Page discovery. From the homepage, find links matching /pricing, /plans, /features, /product, etc. Verify each candidate URL exists.
  3. 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.
  4. Assembly. The canonical JSON is built first, then battlecard_markdown and one_pager_text are 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 plans and a no_pricing_page risk 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_websites URL 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 disclaimer field — travels with every battlecard.
  • Added chargeable_event_count field for transparent billing.
  • Added robots.txt enforcement and per-host rate limiting.
  • Added top-level exception handler — every run produces a structured record.
  • Capped competitors to 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_pricing based on whether structured plans were detected with prices and billing periods.
  • features — set by extract_features based 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.txt
APIFY_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.txt
pytest

107 unit tests, no network, ~150 ms total. Coverage:

  • test_models.py — Pydantic input validation: empty competitors, head-to-head requires our_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_free precision, 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 to candidates_only when 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 and output_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_one in extract_pricing.py / extract_features.py that 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_flags with a pricing_changed code.

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.