Complete Guide
How to build always-up-to-date competitive battle cards powered by AI. From first API call to production integrations with your CRM, Slack, and AI agents.
A competitive battle card is a concise reference document that arms sales reps with everything they need to win against a specific competitor. It is the difference between an account executive stumbling through a competitive question on a call and confidently reframing the conversation in your favour.
The best battle cards are not marketing fluff. They are honest, tactical documents that acknowledge where competitors are strong, identify where they are weak, and provide specific talk tracks for the most common objections your reps face in the field.
71%
of reps say they lack adequate competitive information during sales calls (SiriusDecisions)
23%
higher win rate for teams with up-to-date competitive battle cards (Crayon)
89%
of competitive deals involve the prospect mentioning a specific competitor by name
When a prospect says "We are also looking at Acme Corp," the rep who can immediately speak to Acme's pricing model, known limitations, and differentiators wins. The rep who says "Let me get back to you on that" has already lost momentum.
Battle cards solve this by putting competitive intelligence in a format that is quick to scan, easy to memorize, and directly applicable to sales conversations. But there is a catch: they only work when they are current.
Most companies create battle cards as a one-time project. A product marketing manager spends a week researching each competitor, writes detailed cards, uploads them to the wiki, announces them in Slack, and moves on to the next project.
Within weeks, those cards start to decay:
Competitor updates pricing — your card still shows the old numbers
A rep on a call quotes the competitor's price from two months ago. The prospect corrects them. Credibility evaporates. The rep now questions whether any of the competitive information they have been given is reliable.
Competitor ships a feature you listed as a weakness
Your battle card says "They lack SSO support." The competitor shipped SSO three weeks ago. Your rep confidently makes this claim. The prospect, who just saw the competitor's SSO demo, now trusts you less than the other vendor.
Competitor changes messaging — your positioning is off
Your battle card positions you against a "developer tool for startups." The competitor has repositioned as an "enterprise platform." Your reps are fighting last quarter's battle, not this quarter's.
Nobody owns the update cycle
The PMM who created the cards moved to another project. Nobody is assigned to update them. Months pass. New reps join and receive outdated materials. The battle cards become a historical artifact rather than a living sales tool.
The data
According to Crayon's 2025 State of Competitive Intelligence report, the average battle card is updated only 1.7 times per year. In a market where competitors ship updates monthly and change pricing quarterly, a battle card that is reviewed every six months is a liability, not an asset.
The fundamental insight is that battle cards should not be static documents — they should be generated artifacts that reflect the current state of competitive intelligence. When competitor data changes, the battle card should update.
RivalCheck's battle card system works in three stages:
Continuous monitoring gathers competitive data
RivalCheck monitors competitor pages on a schedule — pricing, features, about, and any custom pages you add. Every change is detected, categorised, and stored with AI analysis. This creates a rich, structured dataset about each competitor that grows over time.
AI generates structured battle cards from raw intelligence
When you request a battle card — via the dashboard or API — RivalCheck's AI processes all available competitive data: current page content, change history, pricing information, and detected positioning. It produces a structured document with standardised sections that sales teams can immediately use.
Cards regenerate when significant changes are detected
When RivalCheck detects a high-severity change — a pricing update, new feature launch, or major messaging shift — the battle card is flagged as stale. You can configure automatic regeneration via webhooks, or trigger it manually through the API. The card always reflects the latest intelligence.
The result is a battle card that is never more than one monitoring cycle behind reality. Instead of a PMM spending 4-8 hours per competitor per quarter manually updating cards, the system keeps them current automatically.
RivalCheck battle cards follow a standardised structure designed for quick scanning during sales calls. Each section serves a specific purpose in the competitive conversation:
Competitor overview
A 2-3 sentence summary of who the competitor is, what they do, and how they position themselves. This is the 30-second context a rep needs before jumping into a call where this competitor comes up unexpectedly.
Their strengths
An honest assessment of where the competitor is strong. This is not about being defeatist — it is about being credible. A rep who acknowledges a competitor's genuine strength and then reframes the conversation earns more trust than one who pretends competitors have no redeeming qualities.
Their weaknesses
Known limitations, gaps in functionality, common customer complaints, and areas where they are behind. These are the wedges your sales team can use — but only if they are current. A weakness that was fixed last month becomes a credibility trap.
Pricing comparison
A side-by-side view of their pricing tiers versus yours. Includes plan names, prices, key feature inclusions, and any notable differences in pricing model (per-seat vs. per-usage, monthly vs. annual-only, etc.). Updated automatically when pricing changes are detected.
How we win against them
Specific differentiators and talk tracks for positioning your product favourably. Not generic claims — concrete examples like "We include SSO on all plans, they gate it behind Enterprise" or "Our average onboarding time is 2 hours vs. their 2-week implementation."
Objection handling
The 3-5 most common objections prospects raise when comparing you to this competitor, with recommended responses. These are generated from competitive positioning analysis and refined over time as the AI observes how the competitor positions against your category.
Elevator pitch
A 2-3 sentence pitch that a rep can use when a prospect asks "How are you different from [Competitor]?" This is the single most useful piece of the battle card — the answer to the question that comes up in every competitive deal.
The Battle Card API provides two endpoints: one to retrieve the current card, and one to trigger regeneration. Both are scoped to a specific competitor.
/api/v1/competitors/:id/battle_card
Retrieve the current battle card for a competitor. Returns the most recently generated card with metadata about freshness.
Parameters
| format | Response format: json (default), markdown, or structured |
Example request:
curl -s -H "Authorization: Bearer $RIVALCHECK_API_KEY" \ "https://app.rivalcheck.com/api/v1/competitors/42/battle_card"
Full JSON response:
{
"battle_card": {
"competitor_id": 42,
"competitor_name": "Acme Corp",
"generated_at": "2026-03-22T10:30:00Z",
"stale": false,
"staleness_reason": null,
"sections": {
"overview": "Acme Corp is a project management platform targeting
mid-market B2B companies. They position themselves as the 'all-in-one
workspace' with strong emphasis on customization and workflow
automation. Founded in 2019, they have raised $45M and serve
approximately 2,000 customers.",
"strengths": [
"Highly customizable workflow engine with 200+ templates",
"Strong brand recognition in the project management space",
"Native integrations with 40+ tools including Jira, Linear, GitHub",
"Generous free tier attracts developers who later advocate internally"
],
"weaknesses": [
"Pricing recently increased 17% on Enterprise tier, causing churn",
"No native time tracking — requires third-party integration",
"Mobile app rated 3.2 stars, frequently cited as unreliable",
"Complex permissions model frustrates administrators",
"Onboarding requires professional services for enterprise deployments"
],
"pricing_comparison": {
"their_plans": [
{ "name": "Starter", "price": "$29/mo", "key_features":
"5 users, basic workflows, 10GB storage" },
{ "name": "Pro", "price": "$79/mo", "key_features":
"25 users, advanced workflows, API access, 100GB storage" },
{ "name": "Enterprise", "price": "$349/mo", "key_features":
"Unlimited users, SSO, custom integrations, SLA" }
],
"key_differences": [
"We include API access on all plans; they gate it behind Pro",
"Their Enterprise is 40% more expensive for comparable features",
"They charge per-workspace; we charge per-organisation (simpler)"
],
"last_pricing_change": "2026-03-22 — Enterprise raised from $299 to $349"
},
"how_we_win": [
"Speed to value: Average setup time is 15 minutes vs their 2-day
onboarding process for teams over 10 people",
"Transparent pricing: No hidden per-seat costs or workspace fees",
"AI-native: Our AI features are built-in, not bolted on.
Theirs requires a separate add-on at additional cost",
"Customer success: We assign a dedicated CSM at Pro tier;
they require Enterprise for dedicated support"
],
"objection_handling": [
{
"objection": "Acme has more integrations than you",
"response": "Acme lists 40+ integrations, but most are shallow
data syncs. We focus on deep, two-way integrations with the
tools your team actually uses. Which integrations are most
important to your workflow? Let me show you how ours work."
},
{
"objection": "We already use Acme and switching costs are high",
"response": "We offer a free migration service and can run in
parallel during the transition. Most teams are fully migrated
in under a week. The question is whether the ongoing cost of
Acme's limitations outweighs a short migration period."
},
{
"objection": "Acme has more customers / bigger brand",
"response": "They have been around longer, absolutely. But
companies our size are switching because their recent price
increases and slower innovation cycle suggest they are
optimizing for margins, not customer value. I can share
three customer stories from companies who made the switch."
}
],
"elevator_pitch": "Unlike Acme Corp, which has grown complex and
expensive as they chase enterprise deals, we are purpose-built for
teams that want powerful competitive intelligence without the
overhead. Our AI-native approach means you get strategic insights
from day one, not after a two-week implementation."
}
}
}
Markdown format:
Pass ?format=markdown to get the battle card as formatted Markdown — ideal for pasting into Notion, Confluence, or Slack:
curl -s -H "Authorization: Bearer $RIVALCHECK_API_KEY" \ "https://app.rivalcheck.com/api/v1/competitors/42/battle_card?format=markdown"
{
"battle_card": {
"competitor_id": 42,
"competitor_name": "Acme Corp",
"generated_at": "2026-03-22T10:30:00Z",
"stale": false,
"format": "markdown",
"content": "# Battle Card: Acme Corp\n\n## Overview\nAcme Corp is a project
management platform targeting mid-market B2B companies...\n\n## Strengths
\n- Highly customizable workflow engine with 200+ templates\n- Strong brand
recognition...\n\n## Weaknesses\n- Pricing recently increased 17%...\n\n
## Pricing Comparison\n| Plan | Their Price | Our Price |\n|------|...\n\n
## How We Win\n1. Speed to value...\n\n## Common Objections\n### \"Acme
has more integrations\"\n...\n\n## Elevator Pitch\n> Unlike Acme Corp..."
}
}
Structured format:
The structured format returns each section as a separate keyed object with both raw content and metadata, useful for programmatic processing:
curl -s -H "Authorization: Bearer $RIVALCHECK_API_KEY" \ "https://app.rivalcheck.com/api/v1/competitors/42/battle_card?format=structured"
/api/v1/competitors/:id/battle_card/generate
Trigger battle card regeneration. The card is generated asynchronously — the endpoint returns a job status, and the new card is available within 30-60 seconds.
Request Body (optional)
| force | Regenerate even if the card is not stale (default: false) |
Example:
curl -X POST -H "Authorization: Bearer $RIVALCHECK_API_KEY" \
-H "Content-Type: application/json" \
-d '{"force": true}' \
"https://app.rivalcheck.com/api/v1/competitors/42/battle_card/generate"
Response:
{
"status": "generating",
"message": "Battle card generation started. Check back in 30-60 seconds.",
"competitor_id": 42,
"estimated_completion": "2026-03-24T15:01:00Z"
}
Battle cards are most valuable when they are accessible in the tools your team already uses. Here are five integration patterns, from simplest to most sophisticated.
Build a Slack slash command that lets reps pull up battle cards instantly. When a rep types /battlecard Acme Corp, they get the key sections right in Slack.
# Slack slash command handler (Python + Flask)
from flask import Flask, request, jsonify
import requests
app = Flask(__name__)
RIVALCHECK_API = "https://app.rivalcheck.com/api/v1"
API_KEY = os.environ["RIVALCHECK_API_KEY"]
@app.route("/slack/battlecard", methods=["POST"])
def battlecard_command():
competitor_name = request.form.get("text", "").strip()
# Find the competitor by name
resp = requests.get(
f"{RIVALCHECK_API}/competitors",
headers={"Authorization": f"Bearer {API_KEY}"}
)
competitors = resp.json()["competitors"]
match = next(
(c for c in competitors
if competitor_name.lower() in c["name"].lower()),
None
)
if not match:
return jsonify({
"response_type": "ephemeral",
"text": f"No competitor found matching '{competitor_name}'."
})
# Fetch the battle card in markdown format
card_resp = requests.get(
f"{RIVALCHECK_API}/competitors/{match['id']}/battle_card?format=markdown",
headers={"Authorization": f"Bearer {API_KEY}"}
)
card = card_resp.json()["battle_card"]
# Format for Slack
sections = card["sections"]
blocks = [
{
"type": "header",
"text": {"type": "plain_text",
"text": f"Battle Card: {match['name']}"}
},
{
"type": "section",
"text": {"type": "mrkdwn",
"text": f"*Overview:* {sections['overview']}"}
},
{"type": "divider"},
{
"type": "section",
"text": {"type": "mrkdwn",
"text": "*How We Win:*\n" + "\n".join(
f"- {point}" for point in sections["how_we_win"]
)}
},
{"type": "divider"},
{
"type": "section",
"text": {"type": "mrkdwn",
"text": f"*Elevator Pitch:*\n>{sections['elevator_pitch']}"}
},
{
"type": "context",
"elements": [{
"type": "mrkdwn",
"text": f"Generated {card['generated_at'][:10]} "
f"| {'Stale' if card['stale'] else 'Current'} "
f"| "
}]
}
]
return jsonify({
"response_type": "in_channel",
"blocks": blocks
})
Sync battle card data into your CRM so reps see competitive intelligence right on the opportunity record. This example updates a custom Salesforce object linked to the Account:
# Sync battle cards to Salesforce custom object
from simple_salesforce import Salesforce
import requests
def sync_battle_cards_to_salesforce():
"""Sync all battle cards to Salesforce Competitive_Card__c records."""
sf = Salesforce(username="...", password="...", security_token="...")
headers = {"Authorization": f"Bearer {API_KEY}"}
# Get all competitors with battle cards
resp = requests.get(f"{RIVALCHECK_API}/competitors", headers=headers)
competitors = resp.json()["competitors"]
for comp in competitors:
if not comp.get("has_battle_card"):
continue
# Fetch the battle card
card_resp = requests.get(
f"{RIVALCHECK_API}/competitors/{comp['id']}/battle_card",
headers=headers
)
card = card_resp.json()["battle_card"]
sections = card["sections"]
# Upsert to Salesforce
sf.Competitive_Card__c.upsert(
f"RivalCheck_ID__c/{comp['id']}",
{
"Name": comp["name"],
"Overview__c": sections["overview"],
"Strengths__c": "\n".join(f"- {s}" for s in sections["strengths"]),
"Weaknesses__c": "\n".join(f"- {w}" for w in sections["weaknesses"]),
"How_We_Win__c": "\n".join(f"- {h}" for h in sections["how_we_win"]),
"Elevator_Pitch__c": sections["elevator_pitch"],
"Pricing_Notes__c": "\n".join(
sections["pricing_comparison"]["key_differences"]
),
"Last_Updated__c": card["generated_at"],
"Is_Stale__c": card["stale"]
}
)
print(f"Synced {len(competitors)} battle cards to Salesforce")
For HubSpot, use their custom objects API with a similar pattern. The battle card data structure maps cleanly to any CRM's custom fields.
Keep a "Competitive Intelligence" page in your team wiki that auto-updates with the latest battle cards. Use the Markdown format for easy syncing:
# Sync battle cards to Notion database
from notion_client import Client
import requests
notion = Client(auth=os.environ["NOTION_TOKEN"])
DATABASE_ID = "your-database-id"
def sync_to_notion():
headers = {"Authorization": f"Bearer {API_KEY}"}
# Get all competitors
resp = requests.get(f"{RIVALCHECK_API}/competitors", headers=headers)
competitors = resp.json()["competitors"]
for comp in competitors:
if not comp.get("has_battle_card"):
continue
# Fetch markdown format
card_resp = requests.get(
f"{RIVALCHECK_API}/competitors/{comp['id']}/battle_card"
f"?format=markdown",
headers=headers
)
card = card_resp.json()["battle_card"]
# Check if page exists in Notion
existing = notion.databases.query(
database_id=DATABASE_ID,
filter={"property": "Competitor",
"title": {"equals": comp["name"]}}
)
page_content = [
{"type": "paragraph", "paragraph": {
"rich_text": [{"type": "text",
"text": {"content": card["content"]}}]
}}
]
if existing["results"]:
# Update existing page
page_id = existing["results"][0]["id"]
# Clear and rewrite content blocks
notion.blocks.children.append(page_id, children=page_content)
else:
# Create new page
notion.pages.create(
parent={"database_id": DATABASE_ID},
properties={
"Competitor": {"title": [{"text": {"content": comp["name"]}}]},
"Status": {"select": {"name": "Current" if not card["stale"]
else "Stale"}},
"Last Updated": {"date": {"start": card["generated_at"][:10]}}
},
children=page_content
)
Use the API to populate competitive slides in your sales deck. Pull the "how we win" and pricing comparison sections to generate a custom competitive slide for each deal:
# Generate a competitive slide using python-pptx
from pptx import Presentation
from pptx.util import Inches, Pt
def create_competitive_slide(competitor_id):
headers = {"Authorization": f"Bearer {API_KEY}"}
card_resp = requests.get(
f"{RIVALCHECK_API}/competitors/{competitor_id}/battle_card",
headers=headers
)
card = card_resp.json()["battle_card"]["sections"]
prs = Presentation("template.pptx")
slide = prs.slides.add_slide(prs.slide_layouts[1])
slide.shapes.title.text = f"Why Us vs {card_resp.json()['battle_card']['competitor_name']}"
body = slide.placeholders[1]
tf = body.text_frame
tf.clear()
for point in card["how_we_win"]:
p = tf.add_paragraph()
p.text = point
p.font.size = Pt(14)
p.level = 0
prs.save(f"competitive_slide_{competitor_id}.pptx")
return f"competitive_slide_{competitor_id}.pptx"
With the RivalCheck MCP server connected, your team can ask Claude for battle card information naturally:
You:
"Get me the battle card for Acme Corp. Focus on how their recent pricing increase affects our competitive positioning."
Claude (via MCP):
Retrieves the battle card and recent changes, then generates a tailored briefing that combines the standard battle card with analysis of the specific pricing change.
You:
"Write me 3 email subject lines I can use to reach out to Acme's customers about this price increase."
This pattern makes battle cards conversational. Instead of reading a document, reps can ask questions and get tailored competitive advice in real time.
The whole point of API-powered battle cards is that they stay current. Here are three strategies for ensuring your cards never go stale:
Configure a webhook to listen for change.detected events. When a high-severity change is detected, automatically regenerate the affected competitor's battle card:
@app.route("/webhooks/rivalcheck", methods=["POST"])
def auto_regenerate_battle_card():
event = request.json
if event["event"] != "change.detected":
return "OK", 200
change = event["data"]
# Only regenerate for high-severity changes
if change["severity"] != "high":
return "OK", 200
competitor_id = change["competitor"]["id"]
# Trigger battle card regeneration
requests.post(
f"{RIVALCHECK_API}/competitors/{competitor_id}/battle_card/generate",
headers={"Authorization": f"Bearer {API_KEY}"},
json={"force": True}
)
# Notify the team
slack_webhook = os.environ["SLACK_WEBHOOK_URL"]
requests.post(slack_webhook, json={
"text": f"Battle card for {change['competitor']['name']} is being "
f"regenerated due to: {change['title']}"
})
return "OK", 200
Even without specific changes, regenerate all battle cards on a schedule (e.g., weekly) to incorporate any cumulative updates. A simple cron job or GitHub Action handles this:
# .github/workflows/regenerate-battle-cards.yml
name: Regenerate Battle Cards
on:
schedule:
- cron: '0 8 * * 1' # Every Monday at 8 AM UTC
jobs:
regenerate:
runs-on: ubuntu-latest
steps:
- name: Regenerate all battle cards
env:
RIVALCHECK_API_KEY: ${{ secrets.RIVALCHECK_API_KEY }}
run: |
# Get all active competitors
COMPETITORS=$(curl -s \
-H "Authorization: Bearer $RIVALCHECK_API_KEY" \
"https://app.rivalcheck.com/api/v1/competitors?status=active")
# Regenerate each battle card
echo "$COMPETITORS" | jq -r '.competitors[].id' | while read id; do
echo "Regenerating battle card for competitor $id..."
curl -X POST \
-H "Authorization: Bearer $RIVALCHECK_API_KEY" \
-H "Content-Type: application/json" \
-d '{"force": true}' \
"https://app.rivalcheck.com/api/v1/competitors/$id/battle_card/generate"
sleep 2 # Rate limiting courtesy
done
Every battle card API response includes a stale boolean and an optional staleness_reason. Use these to flag cards that need attention:
# Check all battle cards for staleness
def audit_battle_card_freshness():
headers = {"Authorization": f"Bearer {API_KEY}"}
resp = requests.get(f"{RIVALCHECK_API}/competitors", headers=headers)
stale_cards = []
for comp in resp.json()["competitors"]:
if not comp.get("has_battle_card"):
continue
card = requests.get(
f"{RIVALCHECK_API}/competitors/{comp['id']}/battle_card",
headers=headers
).json()["battle_card"]
if card["stale"]:
stale_cards.append({
"competitor": comp["name"],
"reason": card["staleness_reason"],
"last_generated": card["generated_at"]
})
if stale_cards:
# Alert the team
message = "Stale battle cards detected:\n"
for card in stale_cards:
message += (f"- {card['competitor']}: {card['reason']} "
f"(last updated {card['last_generated'][:10]})\n")
send_slack_alert(message)
return stale_cards
Here is a complete Python application that ties together all the patterns above: it listens for webhook events, regenerates battle cards when changes are detected, syncs them to Slack and your CRM, and sends a weekly summary to the sales enablement team.
#!/usr/bin/env python3
"""
Battle Card Automation Service
Listens for RivalCheck webhooks, regenerates battle cards,
and syncs them to Slack and Salesforce.
"""
import os
import time
import logging
import requests
from flask import Flask, request, jsonify
from datetime import datetime
app = Flask(__name__)
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("battlecard-service")
RIVALCHECK_API = "https://app.rivalcheck.com/api/v1"
API_KEY = os.environ["RIVALCHECK_API_KEY"]
SLACK_WEBHOOK = os.environ["SLACK_WEBHOOK_URL"]
RC_HEADERS = {"Authorization": f"Bearer {API_KEY}"}
def regenerate_and_wait(competitor_id, max_wait=90):
"""Trigger regeneration and wait for completion."""
# Trigger regeneration
requests.post(
f"{RIVALCHECK_API}/competitors/{competitor_id}/battle_card/generate",
headers=RC_HEADERS,
json={"force": True}
)
# Poll until complete (card is no longer stale)
for _ in range(max_wait // 5):
time.sleep(5)
resp = requests.get(
f"{RIVALCHECK_API}/competitors/{competitor_id}/battle_card",
headers=RC_HEADERS
)
card = resp.json()["battle_card"]
if not card["stale"]:
return card
logger.warning(f"Battle card generation timed out for competitor {competitor_id}")
return None
def notify_slack(competitor_name, change_title, card):
"""Post battle card update notification to Slack."""
sections = card["sections"]
payload = {
"blocks": [
{
"type": "header",
"text": {
"type": "plain_text",
"text": f"Battle Card Updated: {competitor_name}"
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": f"*Triggered by:* {change_title}\n\n"
f"*Key Updates:*\n"
f"- Strengths: {len(sections['strengths'])} items\n"
f"- Weaknesses: {len(sections['weaknesses'])} items\n"
f"- Objections: {len(sections['objection_handling'])} handled"
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": f"*Elevator Pitch:*\n>{sections['elevator_pitch']}"
}
},
{
"type": "actions",
"elements": [{
"type": "button",
"text": {"type": "plain_text", "text": "View Full Card"},
"url": f"https://app.rivalcheck.com/app/competitors/"
f"{card['competitor_id']}/battle_card"
}]
}
]
}
requests.post(SLACK_WEBHOOK, json=payload)
@app.route("/webhooks/rivalcheck", methods=["POST"])
def handle_change():
"""Handle incoming RivalCheck webhook events."""
event = request.json
logger.info(f"Received webhook: {event['event']}")
if event["event"] != "change.detected":
return "OK", 200
change = event["data"]
competitor_id = change["competitor"]["id"]
competitor_name = change["competitor"]["name"]
# Only auto-regenerate for high/medium severity
if change["severity"] not in ("high", "medium"):
logger.info(f"Skipping low-severity change for {competitor_name}")
return "OK", 200
logger.info(f"Regenerating battle card for {competitor_name}")
# Regenerate the battle card
card = regenerate_and_wait(competitor_id)
if card:
# Notify Slack
notify_slack(competitor_name, change["title"], card)
logger.info(f"Battle card updated and Slack notified for {competitor_name}")
else:
# Notify about failure
requests.post(SLACK_WEBHOOK, json={
"text": f"Failed to regenerate battle card for {competitor_name}. "
f"Please check manually."
})
return "OK", 200
@app.route("/health", methods=["GET"])
def health():
return jsonify({"status": "ok", "service": "battlecard-automation"})
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8080)
Deploy this as a service
This Flask app works well on Railway, Render, Fly.io, or any container platform. Set your RIVALCHECK_API_KEY and SLACK_WEBHOOK_URL as environment variables, then point your RivalCheck webhook settings at the /webhooks/rivalcheck endpoint.
RivalCheck generates AI-powered battle cards from the competitive data it collects through automated monitoring. Every plan includes battle card generation — no additional setup required beyond adding competitors.
Get your first battle card in 10 minutes:
We use essential cookies to keep the site working. With your consent, we may also use analytics cookies to improve the experience. Privacy policy.