// Developer Documentation

RivalCheck API Reference

Sourced comparison facts, fact-checked pages, proposed corrections and drafted vs pages — as JSON, so you can build comparison pages on whatever stack you use.

Who can use it: every paid plan, and RivalCheck monitoring customers while subscribed. The same key works with the MCP server.

RivalCheck fact-checks your published comparison, “vs” and alternatives pages against each rival's own website, proposes corrected copy, and drafts the comparison pages you're missing. This API exposes all of it: every sourced fact (with its source URL and the date it was checked), your pages and their findings, proposed corrections you can accept or reject, missing-page gaps, Markdown drafts, and a hash-chained audit log. Use it from a build script, a CMS sync, or CI — or connect your AI coding tool through the MCP server, which calls the same operations.

It's a JSON REST API. Every request needs a Bearer token. Dates are ISO 8601. The base URL is https://rivalcheck.com/api/v1.

Quick Start

1. Get your API key

Go to Settings and create an API key. Keys begin with rc_live_ and are scoped to your organisation. You can revoke and regenerate keys at any time.

2. Make your first request

Pull every sourced fact about one rival:

curl -H "Authorization: Bearer rc_live_YOUR_KEY" \
  "https://rivalcheck.com/api/v1/facts?company=typeform"

3. Build from there

List your pages and their findings, pull proposed corrections, or request a Markdown draft for a rival you don't cover yet — every endpoint is documented below. Working in Claude Code, Cursor or VS Code? Connect the MCP server with the same key.

Authentication

All requests to the RivalCheck competitor monitoring API must include a valid API key in the Authorization header using the Bearer scheme.

Authorization: Bearer rc_live_abc123def456ghi789...

Key management

  • API keys are generated in your account settings.
  • Each key is scoped to a single organisation. All data returned is limited to that organisation's competitors and changes.
  • Keys always start with rc_live_. There are no sandbox/test keys; use a staging organisation for development.
  • You can revoke a key instantly from the settings page. A revoked key returns 401 Unauthorized immediately.
  • Treat your API key like a password. Never commit it to source control. Use environment variables in your applications.

Security tip: If you suspect a key has been compromised, revoke it immediately and generate a new one. Revocation takes effect within seconds.

Rate Limiting

The competitive intelligence API enforces a daily rate limit to ensure fair usage and platform stability. Limits are applied per API key (per organisation).

Current limits

Plan Daily Limit Reset
All plans 1,000 requests/day Midnight UTC

Response headers

Every API response includes rate limit information in the following headers:

Header Description Example
X-RateLimit-Limit Maximum requests allowed per day 1000
X-RateLimit-Remaining Requests remaining in the current window 847
Retry-After Seconds until the limit resets (only present on 429 responses) 3600

Handling rate limits gracefully

When you exceed the daily limit, the API returns a 429 Too Many Requests status with a Retry-After header indicating how many seconds to wait. Best practices:

  • Check X-RateLimit-Remaining proactively and throttle when it drops below a threshold (e.g., 50).
  • Implement exponential backoff when you receive a 429. Start with the Retry-After value, then double on each subsequent retry.
  • Cache responses where possible. Competitor profiles and battle cards change infrequently; the change feed is where freshness matters most.
  • Use webhooks instead of polling. Webhooks do not count against your rate limit and deliver data in real time.

Example 429 response

{
  "error": "rate_limited",
  "message": "Daily API limit exceeded (1000 requests/day). Resets at midnight UTC.",
  "retry_after": 3600
}

Pagination

All list endpoints return paginated results. Pagination is controlled by two query parameters and described by a meta object in every response.

Request parameters

Parameter Type Default Constraints
page integer 1 Minimum 1
limit integer 25 Minimum 1, maximum 100

Response meta object

Every paginated response wraps results in a data array and includes a meta object:

{
  "data": [ ... ],
  "meta": {
    "page": 2,
    "limit": 25,
    "total": 73,
    "total_pages": 3
  }
}

Iterating through all pages

To retrieve all records, keep incrementing page until page equals total_pages. Example in Python:

import requests

all_changes = []
page = 1

while True:
    resp = requests.get(
        "https://rivalcheck.com/api/v1/changes",
        headers={"Authorization": "Bearer rc_live_YOUR_KEY"},
        params={"page": page, "limit": 100}
    )
    data = resp.json()
    all_changes.extend(data["data"])

    if page >= data["meta"]["total_pages"]:
        break
    page += 1

print(f"Fetched {len(all_changes)} total changes")

Errors

The API uses standard HTTP status codes. Error responses have a machine-readable error code and a human-readable message:

{
  "error": "not_found",
  "message": "No page 42."
}
Status Code Meaning
401 unauthorized Missing, invalid or revoked API key
402 payment_required Your plan or monitoring subscription has ended
402 upgrade_required Your organisation doesn't have API access enabled
404 not_found No page, edit or tracked competitor matches the id or name you sent
422 missing_argument / invalid_url A required parameter is missing or the URL can't be fact-checked
422 already_decided The edit has already been accepted, rejected or marked stale
422 limit_reached You already have the maximum number of fact-checked pages or drafts
429 rate_limited Daily limit reached; see the Retry-After header

Content endpoints

Facts, pages, corrections, gaps, drafts and the audit log — the data behind RivalCheck’s setup and monitoring. The MCP server exposes the same operations as tools.

GET /api/v1/facts

Get sourced facts

Every sourced fact about your company and its rivals. Rival values are quoted from the rival’s own website and only appear once verified; each carries its source_url and checked_at time. Your own values are corroborated when your site confirms them, otherwise self_asserted.

Parameters

companyOptional. A rival name, domain or id to narrow the results to your column plus that rival.

Request

curl -H "Authorization: Bearer rc_live_YOUR_KEY" \
  "https://rivalcheck.com/api/v1/facts?company=typeform"

Response

{
  "companies": [
    { "id": 3, "name": "Acme Forms", "role": "self", "website_url": "https://acmeforms.com" },
    { "id": 4, "name": "Typeform", "role": "competitor", "website_url": "https://www.typeform.com" }
  ],
  "facts": [
    {
      "company": "Typeform",
      "company_role": "competitor",
      "dimension": "Starting price",
      "kind": "price",
      "value": "$29/mo",
      "status": "verified",
      "source_url": "https://www.typeform.com/pricing/",
      "checked_at": "2026-09-29T06:12:40Z"
    }
  ],
  "note": "Rival values are quoted from each rival's own website on the checked_at date. Cite source_url when you use one."
}
GET /api/v1/pages

List pages

Your comparison pages: published pages RivalCheck fact-checks (with a findings summary) and drafts RivalCheck wrote. status is pending, ready or failed (with error set).

Parameters

kindOptional. published or draft.

Request

curl -H "Authorization: Bearer rc_live_YOUR_KEY" \
  "https://rivalcheck.com/api/v1/pages?kind=published"

Response

{
  "pages": [
    {
      "id": 12,
      "kind": "published",
      "status": "ready",
      "url": "https://acmeforms.com/vs/typeform",
      "title": "Acme Forms vs Typeform",
      "competitor": null,
      "audited_at": "2026-09-29T07:00:12Z",
      "updated_at": "2026-09-29T07:00:12Z",
      "error": null,
      "summary": {
        "claims": 14, "contradicted": 2, "agrees": 10, "unchecked": 2, "pending_judgement": 0,
        "text": "2 of 14 claims contradict the rival's own site."
      }
    }
  ]
}
GET /api/v1/pages/:id

Get a page

One page in full. Published pages include findings — each with a verdict of contradicted, agrees, minor_difference or unchecked, what your page claims, what the rival’s page says, the source URL and when it was verified. Every page includes its proposed_edits; drafts include markdown with frontmatter.

Request

curl -H "Authorization: Bearer rc_live_YOUR_KEY" \
  "https://rivalcheck.com/api/v1/pages/12"

Response

{
  "page": {
    "id": 12,
    "kind": "published",
    "status": "ready",
    "url": "https://acmeforms.com/vs/typeform",
    "title": "Acme Forms vs Typeform",
    "summary": { "claims": 14, "contradicted": 2, "agrees": 10, "unchecked": 2, "pending_judgement": 0, "text": "..." },
    "findings": [
      {
        "competitor": "Typeform",
        "dimension": "Starting price",
        "verdict": "contradicted",
        "your_claim": "$50/mo",
        "rival_page_says": "$29/mo",
        "source_url": "https://www.typeform.com/pricing/",
        "source_verified_at": "2026-09-29T06:12:40Z",
        "strength": "high",
        "reason": "Typeform's pricing page lists Basic at $29/mo billed annually."
      }
    ],
    "proposed_edits": [ { "id": 88, "status": "proposed", "...": "see /api/v1/edits" } ],
    "markdown": null
  }
}
POST /api/v1/pages

Fact-check a published page

Enrol a page you publish for fact-checking and weekly re-checks. Runs in the background and returns 202 Accepted straight away — poll GET /api/v1/pages/:id until status is ready.

Parameters

urlRequired. The live URL of your comparison, vs or alternatives page.

Request

curl -X POST -H "Authorization: Bearer rc_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://acmeforms.com/vs/typeform" }' \
  https://rivalcheck.com/api/v1/pages

Response

// 202 Accepted
{
  "page": { "id": 12, "kind": "published", "status": "pending", "url": "https://acmeforms.com/vs/typeform", "...": "..." },
  "message": "Reading the page. Poll get_page (GET /api/v1/pages/12) until status is ready — usually a few minutes."
}
GET /api/v1/edits

List proposed corrections

Corrections RivalCheck proposes for your pages: the exact original_text on your page and the suggested_text to replace it with, plus the source that justifies it. Find and replace in your own content, then record the decision.

Parameters

statusOptional. proposed (default), accepted, rejected, stale or all.
page_idOptional. Only edits for this page.

Request

curl -H "Authorization: Bearer rc_live_YOUR_KEY" \
  "https://rivalcheck.com/api/v1/edits?page_id=12&status=proposed"

Response

{
  "edits": [
    {
      "id": 88,
      "page_id": 12,
      "status": "proposed",
      "competitor": "Typeform",
      "dimension": "Starting price",
      "original_text": "Typeform starts at $50/mo.",
      "suggested_text": "Typeform starts at $29/mo (Basic, billed annually).",
      "your_claim": "$50/mo",
      "rival_page_says": "$29/mo",
      "source_url": "https://www.typeform.com/pricing/",
      "source_captured_at": "2026-09-29T06:12:40Z",
      "strength": "high",
      "reason": "Typeform's pricing page lists Basic at $29/mo billed annually.",
      "decided_at": null
    }
  ]
}
GET /api/v1/edits/:id

Get a correction

One proposed correction, in the same shape as the list.

Request

curl -H "Authorization: Bearer rc_live_YOUR_KEY" \
  "https://rivalcheck.com/api/v1/edits/88"

Response

{
  "edit": { "id": 88, "page_id": 12, "status": "proposed", "...": "..." }
}
PATCH /api/v1/edits/:id

Accept or reject a correction

Record your decision. It’s written to the audit log. Only edits still proposed can be decided; anything else returns 422 already_decided.

Parameters

statusRequired. accepted or rejected.

Request

curl -X PATCH -H "Authorization: Bearer rc_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "status": "accepted" }' \
  https://rivalcheck.com/api/v1/edits/88

Response

{
  "edit": { "id": 88, "status": "accepted", "decided_at": "2026-09-30T10:14:02Z", "...": "..." }
}
GET /api/v1/gaps

List missing comparison pages

Rivals you track but don’t have a comparison page (published or drafted) for yet. sourced_facts is how many verified facts RivalCheck holds for that rival; ready_to_draft says whether that’s enough to draft a page.

Request

curl -H "Authorization: Bearer rc_live_YOUR_KEY" \
  "https://rivalcheck.com/api/v1/gaps"

Response

{
  "gaps": [
    {
      "competitor": { "id": 7, "name": "Jotform", "website_url": "https://www.jotform.com" },
      "kind": "versus",
      "suggested_title": "Acme Forms vs Jotform",
      "sourced_facts": 9,
      "ready_to_draft": true,
      "reason": "You compete with Jotform but publish no page comparing the two."
    }
  ]
}
POST /api/v1/drafts

Draft a comparison page

Draft “<Your company> vs <Rival>” as Markdown. The comparison table is quoted from sourced facts — never generated — with footnotes to each source; the prose is fair to the rival. Runs in the background and returns 202 Accepted; poll GET /api/v1/pages/:id for the markdown. If a draft for that rival already exists it’s returned unchanged unless you pass replace: true.

Parameters

competitorRequired. Rival name, domain or id.
briefOptional. Tone and positioning notes. Never used as a source of facts.
replaceOptional boolean. Redraft an existing draft for this rival.

Request

curl -X POST -H "Authorization: Bearer rc_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "competitor": "Typeform", "brief": "Plain, no superlatives", "replace": false }' \
  https://rivalcheck.com/api/v1/drafts

Response

// 202 Accepted
{
  "page": { "id": 15, "kind": "draft", "status": "pending", "title": "Acme Forms vs Typeform", "...": "..." },
  "message": "Drafting. Poll get_page (GET /api/v1/pages/15) until status is ready — usually under a minute."
}
GET /api/v1/ledger

Read the audit log

The hash-chained audit trail, newest first: audits requested, drafts requested, corrections accepted or rejected, and who did it. Each entry’s digest covers the previous entry’s, and chain_intact tells you whether the whole chain verifies. Page back with before_id set to next_before_id.

Parameters

limitOptional. 1–200, default 50.
before_idOptional. Return entries older than this id.

Request

curl -H "Authorization: Bearer rc_live_YOUR_KEY" \
  "https://rivalcheck.com/api/v1/ledger?limit=50"

Response

{
  "entries": [
    {
      "id": 301,
      "event": "edit_accepted",
      "actor": "api",
      "subject": { "type": "proposed_edit", "id": 88 },
      "data": { "...": "..." },
      "recorded_at": "2026-09-30T10:14:02.123456Z",
      "previous_digest": "9f2c…",
      "digest": "41ab…"
    }
  ],
  "chain_intact": true,
  "next_before_id": null
}

Comparison and monitoring endpoints

Your maintained comparison, tracked competitors, change feeds, battle cards and account usage. Same key, same limits. List endpoints here paginate with page and limit (see Pagination).

GET /api/v1/comparison Core resource

Get Your Comparison

Your maintained, evidenced competitor comparison as structured data — every claim carries its verification state, cited source URL, capture date and verification time. Render it in your own site, feed it to your CRM or agents, or diff it between pulls. Competitor claims only reach verified with a source and an archived snapshot; your own claims stay unsubstantiated until your own site corroborates them.

Parameters

companyOptional. Narrow to your column vs the named rival(s) — the head-to-head slice. Comma-separated company names (case-insensitive, Rival Inc or rival-inc) or column ids.

Claim states

verifiedBacked by a cited public source and an archived capture, within the re-check cadence.
changedThe cited live page no longer matches the published value — a proposed change is attached for review.
unsubstantiatedA self-asserted claim your own site doesn’t confirm yet.
unverifiableNo public statement found — itself a competitive finding.

Response

{
  "comparison": {
    "title": "Acme vs Rival Inc and CompeteCo",
    "slug": "acme-vs-the-field",
    "published": true,
    "public_url": "https://rivalcheck.com/comparisons/acme-vs-the-field",
    "reverify_cadence": "daily",
    "last_reverified_at": "2026-07-02T05:04:11Z",
    "next_reverify_at": "2026-07-03T05:04:11Z",
    "companies": [
      { "id": 3, "name": "Acme", "role": "self", "website_url": "https://acme.com" },
      { "id": 4, "name": "Rival Inc", "role": "competitor", "website_url": "https://rival.com" }
    ],
    "dimensions": [
      {
        "id": 2,
        "name": "Starting price",
        "kind": "price",
        "claims": [
          {
            "company_id": 4,
            "company": "Rival Inc",
            "value": "$119/mo",
            "state": "verified",
            "needs_review": false,
            "self_asserted": false,
            "source_url": "https://rival.com/pricing",
            "source_captured_at": "2026-07-02T05:04:09Z",
            "verified_at": "2026-07-02T05:04:11Z",
            "proposed_change": null
          }
        ]
      }
    ]
  },
  "meta": { "claims": 24, "verified": 19, "liabilities": 1, "generated_at": "2026-07-02T09:00:00Z" }
}
GET /api/v1/comparison/liabilities

List Claims at Risk

Claims whose cited live page no longer matches what you publish — each with the standing value, the proposed replacement, and the evidence behind it. Poll this (or subscribe to the comparison.liability webhook event) to route drift into Slack, your CRM, or an agent workflow.

Response

{
  "liabilities": [
    {
      "dimension": "Public API",
      "dimension_id": 5,
      "company": "Rival Inc",
      "company_id": 4,
      "company_role": "competitor",
      "published_value": "None",
      "published_source_url": "https://rival.com/docs",
      "last_verified_at": "2026-06-24T19:13:32Z",
      "proposed_change": {
        "value": "Beta",
        "source_url": "https://rival.com/changelog",
        "captured_at": "2026-07-02T07:12:00Z"
      }
    }
  ],
  "meta": { "count": 1, "generated_at": "2026-07-02T09:00:00Z" }
}
GET /api/v1/competitors

List Competitors

Returns a paginated list of all competitors your organisation is monitoring. This is typically the starting point for any integration with the competitor monitoring API: fetch your competitor list, then drill into changes or battle cards for each.

Parameters

Name Type Default Description
status string all Filter by status. One of: pending, discovering, active, paused, error, review_pages
page integer 1 Page number (min 1)
limit integer 25 Results per page (min 1, max 100)

Response

{
  "data": [
    {
      "id": "comp_8xKm2Nq",
      "name": "Acme Corp",
      "website_url": "https://www.acmecorp.com",
      "status": "active",
      "pages_count": 4,
      "changes_count": 17,
      "last_checked_at": "2026-03-24T06:15:00Z",
      "last_change_at": "2026-03-22T14:33:00Z",
      "has_battle_card": true,
      "created_at": "2026-02-10T09:00:00Z"
    },
    {
      "id": "comp_3pRtYvW",
      "name": "BetaRival",
      "website_url": "https://betarival.io",
      "status": "active",
      "pages_count": 3,
      "changes_count": 8,
      "last_checked_at": "2026-03-24T06:20:00Z",
      "last_change_at": "2026-03-19T10:05:00Z",
      "has_battle_card": true,
      "created_at": "2026-02-15T11:30:00Z"
    },
    {
      "id": "comp_9dLwZxJ",
      "name": "NewStart.io",
      "website_url": "https://newstart.io",
      "status": "discovering",
      "pages_count": 0,
      "changes_count": 0,
      "last_checked_at": null,
      "last_change_at": null,
      "has_battle_card": false,
      "created_at": "2026-03-24T08:00:00Z"
    }
  ],
  "meta": {
    "page": 1,
    "limit": 25,
    "total": 3,
    "total_pages": 1
  }
}

Competitor statuses explained

pending Just created. Discovery has not started yet.
discovering RivalCheck is crawling the website to identify key pages (pricing, product, about, etc.).
review_pages Discovery is complete. Pages have been suggested and are awaiting your review before monitoring begins.
active Actively monitored. Pages are checked regularly and changes are detected automatically.
paused Monitoring paused by user. No checks are performed. Historical data is retained.
error An error occurred during discovery or monitoring (e.g., site unreachable, blocked by firewall).
GET /api/v1/competitors/:id

Get Competitor Detail

Returns the full profile of a single competitor, including all monitored pages, the five most recent changes, and battle card availability. This endpoint is ideal for building competitor detail views in custom dashboards or for feeding context to an AI agent preparing a sales call briefing.

Path parameters

id string, required The competitor's public ID (e.g., comp_8xKm2Nq)

Response

{
  "data": {
    "id": "comp_8xKm2Nq",
    "name": "Acme Corp",
    "website_url": "https://www.acmecorp.com",
    "status": "active",
    "last_checked_at": "2026-03-24T06:15:00Z",
    "last_change_at": "2026-03-22T14:33:00Z",
    "has_battle_card": true,
    "battle_card_generated_at": "2026-03-20T12:00:00Z",
    "created_at": "2026-02-10T09:00:00Z",
    "pages": [
      {
        "id": "page_Kx8mNq2",
        "url": "https://www.acmecorp.com/pricing",
        "page_type": "pricing",
        "last_checked_at": "2026-03-24T06:15:00Z",
        "last_change_at": "2026-03-22T14:33:00Z",
        "changes_count": 5
      },
      {
        "id": "page_Rt3pYvW",
        "url": "https://www.acmecorp.com/product",
        "page_type": "product",
        "last_checked_at": "2026-03-24T06:15:00Z",
        "last_change_at": "2026-03-15T09:20:00Z",
        "changes_count": 7
      },
      {
        "id": "page_Lw9dZxJ",
        "url": "https://www.acmecorp.com/about",
        "page_type": "about",
        "last_checked_at": "2026-03-24T06:15:00Z",
        "last_change_at": "2026-03-01T16:45:00Z",
        "changes_count": 3
      },
      {
        "id": "page_Qn7bMvF",
        "url": "https://www.acmecorp.com/enterprise",
        "page_type": "landing",
        "last_checked_at": "2026-03-24T06:15:00Z",
        "last_change_at": "2026-03-18T11:10:00Z",
        "changes_count": 2
      }
    ],
    "recent_changes": [
      {
        "id": "chg_Xp4kLm8",
        "change_type": "pricing",
        "severity": "major",
        "summary": "Enterprise tier price increased from $299/mo to $349/mo. New 'Scale' tier added at $199/mo.",
        "detected_at": "2026-03-22T14:33:00Z"
      },
      {
        "id": "chg_Yn5jKn9",
        "change_type": "content",
        "severity": "moderate",
        "summary": "Product page updated with new AI features section. Three new feature cards added highlighting 'AI Assistant', 'Smart Reports', and 'Predictive Analytics'.",
        "detected_at": "2026-03-15T09:20:00Z"
      }
    ]
  }
}
POST /api/v1/competitors

Add a Competitor

Start monitoring a new competitor. RivalCheck will automatically crawl the website, identify key pages (pricing, product, about, blog, etc.), and begin monitoring them for changes. The competitor begins in pending status and progresses through discovering before becoming active. This process typically takes 1-3 minutes.

Request body

Field Type Required Description
website_url string Yes Full URL of the competitor's website. Must start with http:// or https://.
name string No Display name. If omitted, RivalCheck extracts the company name from the website automatically.

Request example

curl -X POST https://rivalcheck.com/api/v1/competitors \
  -H "Authorization: Bearer rc_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "website_url": "https://www.newrival.com",
    "name": "NewRival"
  }'

Response (201 Created)

{
  "data": {
    "id": "comp_Hk7nRt4",
    "name": "NewRival",
    "website_url": "https://www.newrival.com",
    "status": "pending",
    "pages_count": 0,
    "changes_count": 0,
    "last_checked_at": null,
    "last_change_at": null,
    "has_battle_card": false,
    "created_at": "2026-03-24T10:30:00Z"
  },
  "message": "Competitor added. Discovery will begin shortly."
}

Tip: After creating a competitor, poll GET /api/v1/competitors/:id to check when the status transitions to active. Alternatively, configure a webhook for the competitor.status_changed event.

GET /api/v1/competitors/:id/changes

List Competitor Changes

Returns a paginated, filterable list of all changes detected for a specific competitor. Use this when you need a focused view of a single rival's activity, for example when preparing for a deal against that competitor or building a competitor-specific report.

Path parameters

id string, required The competitor's public ID

Query parameters

Name Type Default Description
severity string all Filter by severity: minor, moderate, major
type string all Filter by change type: pricing, content, layout
since string none ISO 8601 datetime. Only return changes detected after this timestamp. Example: 2026-03-01T00:00:00Z
page integer 1 Page number
limit integer 25 Results per page (max 100)

Response

{
  "data": [
    {
      "id": "chg_Xp4kLm8",
      "competitor_id": "comp_8xKm2Nq",
      "competitor_name": "Acme Corp",
      "page_url": "https://www.acmecorp.com/pricing",
      "page_type": "pricing",
      "change_type": "pricing",
      "severity": "major",
      "summary": "Enterprise tier price increased from $299/mo to $349/mo. New 'Scale' tier added at $199/mo.",
      "detected_at": "2026-03-22T14:33:00Z",
      "reviewed": false,
      "bookmarked": false
    },
    {
      "id": "chg_Yn5jKn9",
      "competitor_id": "comp_8xKm2Nq",
      "competitor_name": "Acme Corp",
      "page_url": "https://www.acmecorp.com/product",
      "page_type": "product",
      "change_type": "content",
      "severity": "moderate",
      "summary": "Product page updated with new AI features section. Three new feature cards added.",
      "detected_at": "2026-03-15T09:20:00Z",
      "reviewed": true,
      "bookmarked": false
    }
  ],
  "meta": {
    "page": 1,
    "limit": 25,
    "total": 17,
    "total_pages": 1
  }
}
GET /api/v1/changes

List All Changes (Organisation-Wide Feed)

Returns a unified, paginated feed of changes across all competitors in your organisation. This is the most powerful endpoint in the competitive intelligence API for building dashboards, generating weekly briefings, or feeding a CI aggregation pipeline. Supports filtering by severity, type, competitor, date range, and full-text search.

Query parameters

Name Type Default Description
severity string all minor, moderate, or major
type string all pricing, content, or layout
competitor_id string none Filter to a specific competitor's public ID
since string none ISO 8601 datetime. Only changes after this timestamp.
search string none Full-text search across change summaries and AI analysis. Example: price increase
page integer 1 Page number
limit integer 25 Results per page (max 100)

Example: Fetch major pricing changes from the past 7 days

curl -G https://rivalcheck.com/api/v1/changes \
  -H "Authorization: Bearer rc_live_YOUR_KEY" \
  -d severity=major \
  -d type=pricing \
  -d since=2026-03-17T00:00:00Z

Response

{
  "data": [
    {
      "id": "chg_Xp4kLm8",
      "competitor_id": "comp_8xKm2Nq",
      "competitor_name": "Acme Corp",
      "page_url": "https://www.acmecorp.com/pricing",
      "page_type": "pricing",
      "change_type": "pricing",
      "severity": "major",
      "summary": "Enterprise tier price increased from $299/mo to $349/mo. New 'Scale' tier added at $199/mo.",
      "detected_at": "2026-03-22T14:33:00Z",
      "reviewed": false,
      "bookmarked": false
    },
    {
      "id": "chg_Bm2wQt7",
      "competitor_id": "comp_3pRtYvW",
      "competitor_name": "BetaRival",
      "page_url": "https://betarival.io/pricing",
      "page_type": "pricing",
      "change_type": "pricing",
      "severity": "major",
      "summary": "Free tier removed entirely. Lowest plan now starts at $29/mo (previously had a free plan with 100 API calls).",
      "detected_at": "2026-03-19T10:05:00Z",
      "reviewed": true,
      "bookmarked": true
    }
  ],
  "meta": {
    "page": 1,
    "limit": 25,
    "total": 2,
    "total_pages": 1
  }
}

Change types and severity levels

Change types

pricing Price adjustments, new tiers, removed plans, trial changes
content Copy changes, new features, messaging shifts, positioning updates
layout Visual redesigns, new sections, structural reorganization

Severity levels

major Significant strategic shifts, large price changes, new product launches
moderate Notable changes worth tracking: new features, messaging updates, minor pricing tweaks
minor Small copy edits, typo fixes, cosmetic adjustments
GET /api/v1/changes/:id

Get Change Detail

Returns the full detail of a specific change, including the AI-generated analysis, before/after snapshots, and A/B testing detection. This is the deepest endpoint in the competitor analysis API, providing everything you need to understand exactly what a competitor changed and what it might mean strategically.

Path parameters

id string, required The change's public ID (e.g., chg_Xp4kLm8)

Response

{
  "data": {
    "id": "chg_Xp4kLm8",
    "competitor_id": "comp_8xKm2Nq",
    "competitor_name": "Acme Corp",
    "page_url": "https://www.acmecorp.com/pricing",
    "page_type": "pricing",
    "change_type": "pricing",
    "severity": "major",
    "summary": "Enterprise tier price increased from $299/mo to $349/mo. New 'Scale' tier added at $199/mo.",
    "detected_at": "2026-03-22T14:33:00Z",
    "reviewed": false,
    "bookmarked": false,
    "ai_analysis": {
      "what_changed": "Acme Corp restructured their pricing page with two significant modifications. The Enterprise tier saw a 16.7% price increase from $299/month to $349/month. Simultaneously, they introduced a new 'Scale' tier positioned between their existing Pro ($99/mo) and Enterprise offerings at $199/month.",
      "why_it_matters": "This pricing restructure signals that Acme Corp is moving upmarket. The new Scale tier creates a smoother upgrade path, potentially reducing churn at the Pro level while extracting more revenue from mid-market customers. The Enterprise price increase suggests strong demand at that level and confidence in their enterprise value proposition.",
      "recommended_action": "Review your own mid-market pricing. If you do not have a comparable tier between $150-$250/month, you may lose deals to Acme's new Scale plan. Consider whether your Enterprise pricing should also be adjusted. Brief the sales team on this change before upcoming deals against Acme.",
      "competitive_impact": "high"
    },
    "ab_test_detected": false,
    "snapshots": {
      "before": {
        "captured_at": "2026-03-20T06:00:00Z",
        "content_hash": "a1b2c3d4e5f6..."
      },
      "after": {
        "captured_at": "2026-03-22T14:30:00Z",
        "content_hash": "f6e5d4c3b2a1..."
      }
    }
  }
}

About A/B testing detection: When RivalCheck detects that a page is alternating between different versions (e.g., the pricing page shows different prices on different visits), the ab_test_detected field is set to true. This is crucial for the pricing change detection API use case, as it prevents false positives from being treated as permanent changes.

GET /api/v1/competitors/:id/battle_card

Get Battle Card

Retrieve the AI-generated battle card for a competitor. Battle cards are a cornerstone of competitive sales enablement, and the battle card API lets you embed them directly into your CRM, sales playbooks, or AI-assisted deal preparation workflows. Cards are generated from the latest monitoring data and include strengths, weaknesses, pricing comparisons, objection handling scripts, and elevator pitches.

Path parameters

id string, required The competitor's public ID

Query parameters

Name Type Default Description
format_type string full full returns all sections in a single structured JSON object. markdown returns the entire card as a formatted Markdown string (ideal for Slack, Notion, or AI agents). structured returns sections as an array with section keys and content.

Response (format_type=full)

{
  "data": {
    "id": "bc_Vm3xKp7",
    "competitor_id": "comp_8xKm2Nq",
    "competitor_name": "Acme Corp",
    "generated_at": "2026-03-20T12:00:00Z",
    "stale": false,
    "sections": {
      "overview": "Acme Corp is a mid-market B2B SaaS platform focused on project management and team collaboration. Founded in 2019, they have raised $45M in Series B funding and serve approximately 2,000 customers. They position themselves as the 'all-in-one workspace' for growing teams, competing primarily on breadth of features rather than depth in any single area.",
      "strengths": [
        "Strong brand recognition in the 50-200 employee segment",
        "Comprehensive feature set spanning project management, docs, and chat",
        "Aggressive content marketing with high-ranking SEO pages",
        "Recently added AI features that are getting positive early reviews",
        "Free tier available, making bottom-up adoption easy"
      ],
      "weaknesses": [
        "Enterprise tier pricing recently increased 16.7%, causing customer complaints on social media",
        "No native integrations with industry-specific tools (e.g., Figma, GitHub)",
        "Mobile app has 3.2-star rating on App Store with complaints about performance",
        "No SOC 2 Type II certification yet (in progress per their security page)",
        "Customer support response times averaging 24+ hours based on review sites"
      ],
      "pricing_comparison": {
        "their_plans": [
          {"name": "Free", "price": "$0/mo", "notes": "Up to 5 users, limited features"},
          {"name": "Pro", "price": "$99/mo", "notes": "Up to 25 users, all core features"},
          {"name": "Scale", "price": "$199/mo", "notes": "NEW - Up to 100 users, advanced analytics"},
          {"name": "Enterprise", "price": "$349/mo", "notes": "Unlimited users, SSO, priority support"}
        ],
        "key_differences": "Acme prices per-workspace while we price per-user. For teams of 10-30, Acme is typically cheaper. Above 30 users, our pricing becomes more competitive. Their new Scale tier directly targets the segment where we have historically won deals.",
        "talk_track": "When a prospect mentions Acme's pricing, emphasize our per-user model's predictability and the fact that our Pro plan includes features they charge Enterprise prices for (SSO, API access, custom roles)."
      },
      "how_we_win": [
        "Lead with our superior integration ecosystem (200+ native integrations vs their 45)",
        "Demonstrate our mobile experience side-by-side — our 4.7-star app vs their 3.2",
        "Emphasize our SOC 2 Type II and GDPR compliance for security-conscious buyers",
        "For larger teams (30+), show the TCO comparison where our per-user pricing wins",
        "Highlight our 4-hour average support response time with customer testimonials"
      ],
      "objection_handling": [
        {
          "objection": "Acme has a free tier and you don't.",
          "response": "That's true — Acme's free tier is designed to get teams started, but it's limited to 5 users with no integrations or reporting. Our 14-day trial gives you full access to everything, so you can make a real evaluation. Most teams that try both choose us because they see the depth difference immediately."
        },
        {
          "objection": "Acme just launched AI features.",
          "response": "We've had AI-powered insights for over a year now. Acme's AI is a first-generation addition. Ask to see a side-by-side comparison of the AI outputs — ours are more actionable because they're trained on deeper data integrations. We're happy to do a proof-of-concept."
        },
        {
          "objection": "Acme seems more established.",
          "response": "Acme has strong brand awareness, but look at the trajectory. We've grown 3x year-over-year and have a higher NPS score (72 vs their 54). More importantly, look at which direction each product is heading. Their recent pricing increases and feature additions are playing catch-up in areas where we've led for years."
        }
      ],
      "elevator_pitch": "While Acme Corp offers a broad but shallow all-in-one workspace, we provide deeper functionality where it matters most — with 200+ integrations, enterprise-grade security, award-winning mobile experience, and AI that's been battle-tested for over a year. Teams that evaluate both choose us 68% of the time because of the depth difference."
    }
  }
}

Battle card sections

Section Description
overview Company background, positioning, market segment, and recent trajectory
strengths Array of the competitor's key advantages and differentiators
weaknesses Array of known weaknesses, gaps, and vulnerabilities
pricing_comparison Detailed pricing breakdown with plans, key differences, and sales talk tracks
how_we_win Array of specific strategies and tactics for winning deals against this competitor
objection_handling Array of common objections with recommended responses
elevator_pitch A concise summary positioning you against this specific competitor

Staleness: The stale field is true when significant changes have been detected since the battle card was last generated. Use the generate endpoint to refresh it. See our guide on building with the battle cards API for best practices.

POST /api/v1/competitors/:id/battle_card/generate

Generate or Regenerate Battle Card

Trigger the (re)generation of a battle card for the specified competitor. This is an asynchronous operation: the endpoint returns immediately with a 202 Accepted status. Generation typically takes 15-30 seconds. Poll the GET battle_card endpoint to check for completion, or listen for the battle_card.updated webhook event.

Path parameters

id string, required The competitor's public ID

Request example

curl -X POST https://rivalcheck.com/api/v1/competitors/comp_8xKm2Nq/battle_card/generate \
  -H "Authorization: Bearer rc_live_YOUR_KEY"

Response (202 Accepted)

{
  "message": "Battle card generation started.",
  "competitor_id": "comp_8xKm2Nq",
  "estimated_seconds": 20,
  "poll_url": "/api/v1/competitors/comp_8xKm2Nq/battle_card"
}

Polling pattern: After triggering generation, wait 5 seconds, then poll the GET endpoint every 5 seconds until generated_at is more recent than your trigger time. Or, simply use a webhook for a push-based approach.

GET /api/v1/landscape

Competitive Landscape Summary

Returns a high-level overview of your entire competitive landscape: all competitors with their activity metrics, severity breakdowns, change type distributions, and the latest pricing intelligence. This endpoint powers executive dashboards and weekly competitive briefings. It is one of the most valuable endpoints in the competitive intelligence API for teams that need a bird's-eye view.

Response

{
  "data": {
    "total_competitors": 5,
    "total_changes_30d": 34,
    "severity_breakdown": {
      "major": 4,
      "moderate": 12,
      "minor": 18
    },
    "type_breakdown": {
      "pricing": 6,
      "content": 19,
      "layout": 9
    },
    "competitors": [
      {
        "id": "comp_8xKm2Nq",
        "name": "Acme Corp",
        "status": "active",
        "changes_30d": 12,
        "last_change_at": "2026-03-22T14:33:00Z",
        "severity_breakdown": {"major": 2, "moderate": 5, "minor": 5},
        "type_breakdown": {"pricing": 3, "content": 6, "layout": 3},
        "latest_pricing_change": {
          "id": "chg_Xp4kLm8",
          "summary": "Enterprise tier price increased from $299/mo to $349/mo. New 'Scale' tier added at $199/mo.",
          "detected_at": "2026-03-22T14:33:00Z",
          "severity": "major"
        }
      },
      {
        "id": "comp_3pRtYvW",
        "name": "BetaRival",
        "status": "active",
        "changes_30d": 8,
        "last_change_at": "2026-03-19T10:05:00Z",
        "severity_breakdown": {"major": 1, "moderate": 3, "minor": 4},
        "type_breakdown": {"pricing": 1, "content": 5, "layout": 2},
        "latest_pricing_change": {
          "id": "chg_Bm2wQt7",
          "summary": "Free tier removed. Lowest plan now $29/mo.",
          "detected_at": "2026-03-19T10:05:00Z",
          "severity": "major"
        }
      },
      {
        "id": "comp_Zt6mHg3",
        "name": "Gamma Solutions",
        "status": "active",
        "changes_30d": 14,
        "last_change_at": "2026-03-23T08:12:00Z",
        "severity_breakdown": {"major": 1, "moderate": 4, "minor": 9},
        "type_breakdown": {"pricing": 2, "content": 8, "layout": 4},
        "latest_pricing_change": null
      }
    ],
    "generated_at": "2026-03-24T10:00:00Z"
  }
}
GET /api/v1/account

Account Information

Returns your current plan details, usage statistics, and feature limits. Useful for building usage dashboards, enforcing client-side limits, or checking whether you can add more competitors before making a POST request.

Response

{
  "data": {
    "organisation": {
      "id": "org_Qm4xNr8",
      "name": "My Company Inc"
    },
    "plan": {
      "name": "Growth",
      "interval": "monthly",
      "price_cents": 4900,
      "currency": "usd"
    },
    "usage": {
      "competitors_used": 8,
      "competitors_limit": 15,
      "pages_used": 32,
      "pages_limit": 75,
      "api_requests_today": 153,
      "api_requests_limit": 1000
    },
    "features": {
      "battle_cards": true,
      "webhooks": true,
      "mcp_access": true,
      "api_access": true,
      "email_digests": true,
      "custom_pages": true
    }
  }
}

Code Examples

Complete, copy-paste-ready examples for the most common competitor tracking API operations in multiple languages.

Fetch Recent Pricing Changes

This is the most common use case for the pricing change detection API: retrieve all pricing changes from the past 7 days so your team can respond quickly.

curl

curl -G https://rivalcheck.com/api/v1/changes \
  -H "Authorization: Bearer rc_live_YOUR_KEY" \
  -d type=pricing \
  -d since=2026-03-17T00:00:00Z \
  -d limit=50

Python

import requests
from datetime import datetime, timedelta

api_key = "rc_live_YOUR_KEY"
seven_days_ago = (datetime.utcnow() - timedelta(days=7)).isoformat() + "Z"

response = requests.get(
    "https://rivalcheck.com/api/v1/changes",
    headers={"Authorization": f"Bearer {api_key}"},
    params={
        "type": "pricing",
        "since": seven_days_ago,
        "limit": 50,
    },
)
response.raise_for_status()
data = response.json()

for change in data["data"]:
    print(f"[{change['severity'].upper()}] {change['competitor_name']}")
    print(f"  {change['summary']}")
    print(f"  Detected: {change['detected_at']}")
    print()

JavaScript (Node.js / fetch)

const API_KEY = process.env.RIVALCHECK_API_KEY;
const sevenDaysAgo = new Date(Date.now() - 7 * 86400000).toISOString();

const params = new URLSearchParams({
  type: "pricing",
  since: sevenDaysAgo,
  limit: "50",
});

const response = await fetch(
  `https://rivalcheck.com/api/v1/changes?${params}`,
  {
    headers: { Authorization: `Bearer ${API_KEY}` },
  }
);

if (!response.ok) {
  const err = await response.json();
  throw new Error(`API error: ${err.error.message}`);
}

const { data, meta } = await response.json();

console.log(`Found ${meta.total} pricing changes in the last 7 days:\n`);
for (const change of data) {
  console.log(`[${change.severity.toUpperCase()}] ${change.competitor_name}`);
  console.log(`  ${change.summary}`);
  console.log(`  Detected: ${change.detected_at}\n`);
}

Ruby

require "net/http"
require "json"
require "uri"

api_key = ENV.fetch("RIVALCHECK_API_KEY")
since = (Time.now.utc - 7 * 86_400).iso8601

uri = URI("https://rivalcheck.com/api/v1/changes")
uri.query = URI.encode_www_form(type: "pricing", since: since, limit: 50)

request = Net::HTTP::Get.new(uri)
request["Authorization"] = "Bearer #{api_key}"

response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
  http.request(request)
end

data = JSON.parse(response.body)

data["data"].each do |change|
  puts "[#{change['severity'].upcase}] #{change['competitor_name']}"
  puts "  #{change['summary']}"
  puts "  Detected: #{change['detected_at']}"
  puts
end

Fetch a Battle Card for Sales Prep

Retrieve a battle card in markdown format, ready to paste into Slack, Notion, or any other tool your sales team uses. The battle card API supports multiple output formats for exactly this purpose.

curl

curl -G https://rivalcheck.com/api/v1/competitors/comp_8xKm2Nq/battle_card \
  -H "Authorization: Bearer rc_live_YOUR_KEY" \
  -d format_type=markdown

Python

import requests

api_key = "rc_live_YOUR_KEY"
competitor_id = "comp_8xKm2Nq"

response = requests.get(
    f"https://rivalcheck.com/api/v1/competitors/{competitor_id}/battle_card",
    headers={"Authorization": f"Bearer {api_key}"},
    params={"format_type": "markdown"},
)
response.raise_for_status()
card = response.json()["data"]

# Check if the card is stale and needs regeneration
if card["stale"]:
    print("Warning: Battle card is stale. Consider regenerating.")
    regen = requests.post(
        f"https://rivalcheck.com/api/v1/competitors/{competitor_id}/battle_card/generate",
        headers={"Authorization": f"Bearer {api_key}"},
    )
    print(f"Regeneration triggered: {regen.json()['message']}")

print(card["markdown"])

JavaScript

const API_KEY = process.env.RIVALCHECK_API_KEY;
const competitorId = "comp_8xKm2Nq";

const params = new URLSearchParams({ format_type: "markdown" });
const response = await fetch(
  `https://rivalcheck.com/api/v1/competitors/${competitorId}/battle_card?${params}`,
  { headers: { Authorization: `Bearer ${API_KEY}` } }
);

const { data: card } = await response.json();

if (card.stale) {
  console.warn("Battle card is stale. Triggering regeneration...");
  await fetch(
    `https://rivalcheck.com/api/v1/competitors/${competitorId}/battle_card/generate`,
    { method: "POST", headers: { Authorization: `Bearer ${API_KEY}` } }
  );
}

console.log(card.markdown);

Ruby

require "net/http"
require "json"

api_key = ENV.fetch("RIVALCHECK_API_KEY")
competitor_id = "comp_8xKm2Nq"

uri = URI("https://rivalcheck.com/api/v1/competitors/#{competitor_id}/battle_card")
uri.query = URI.encode_www_form(format_type: "markdown")

request = Net::HTTP::Get.new(uri)
request["Authorization"] = "Bearer #{api_key}"

response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
  http.request(request)
end

card = JSON.parse(response.body)["data"]
puts card["stale"] ? "Warning: card is stale" : "Card is fresh"
puts card["markdown"]

Add a Competitor and Wait for Discovery

Programmatically add a new competitor and poll until discovery is complete. This pattern is useful for onboarding flows or bulk competitor imports.

Python

import requests
import time

api_key = "rc_live_YOUR_KEY"
headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}

# Step 1: Add the competitor
create_resp = requests.post(
    "https://rivalcheck.com/api/v1/competitors",
    headers=headers,
    json={"website_url": "https://www.newrival.com", "name": "NewRival"},
)
create_resp.raise_for_status()
competitor = create_resp.json()["data"]
comp_id = competitor["id"]
print(f"Created competitor: {comp_id} (status: {competitor['status']})")

# Step 2: Poll until active
max_attempts = 30
for attempt in range(max_attempts):
    time.sleep(10)  # Check every 10 seconds
    detail_resp = requests.get(
        f"https://rivalcheck.com/api/v1/competitors/{comp_id}",
        headers=headers,
    )
    status = detail_resp.json()["data"]["status"]
    print(f"  Attempt {attempt + 1}: status = {status}")

    if status == "active":
        pages = detail_resp.json()["data"]["pages"]
        print(f"Discovery complete! Monitoring {len(pages)} pages.")
        break
    elif status == "error":
        print("Discovery failed. Check the website URL.")
        break
else:
    print("Timed out waiting for discovery.")

JavaScript

const API_KEY = process.env.RIVALCHECK_API_KEY;
const headers = {
  Authorization: `Bearer ${API_KEY}`,
  "Content-Type": "application/json",
};

// Step 1: Add the competitor
const createResp = await fetch("https://rivalcheck.com/api/v1/competitors", {
  method: "POST",
  headers,
  body: JSON.stringify({
    website_url: "https://www.newrival.com",
    name: "NewRival",
  }),
});
const { data: competitor } = await createResp.json();
console.log(`Created: ${competitor.id} (${competitor.status})`);

// Step 2: Poll until active
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

for (let attempt = 0; attempt < 30; attempt++) {
  await sleep(10_000);
  const resp = await fetch(
    `https://rivalcheck.com/api/v1/competitors/${competitor.id}`,
    { headers }
  );
  const { data } = await resp.json();
  console.log(`  Attempt ${attempt + 1}: ${data.status}`);

  if (data.status === "active") {
    console.log(`Discovery complete! Monitoring ${data.pages.length} pages.`);
    break;
  }
  if (data.status === "error") {
    console.error("Discovery failed.");
    break;
  }
}

Real-World Scenarios

Practical patterns showing how teams use the competitor monitoring API in production.

Automated Pricing Alert Pipeline

A SaaS company runs a cron job every morning that calls GET /api/v1/changes?type=pricing&since=<yesterday>&severity=major. If results are returned, it posts a formatted message to the #competitive-intel Slack channel, tags the VP of Product, and creates a task in Linear. The pricing change detection API ensures no pricing move goes unnoticed.

Endpoints used: GET /changes

AI-Powered Sales Call Prep

Before every sales call, an AI agent (via MCP or API) fetches the battle card for the deal's primary competitor and the last 30 days of changes. It generates a custom briefing document with talking points, objection responses, and a pricing comparison table. The sales rep receives this in their inbox 30 minutes before the call. The battle card API and competitor analysis API work together here.

Endpoints used: GET /competitors/:id/battle_card, GET /competitors/:id/changes

Weekly Board-Ready Competitive Report

Every Monday, a scheduled script calls GET /api/v1/landscape and GET /api/v1/changes?since=<7_days_ago>, then feeds the results to an LLM with a prompt template to generate an executive-ready competitive briefing as a PDF. The report covers competitor activity trends, notable moves, and strategic implications.

Endpoints used: GET /landscape, GET /changes

CRM Integration for Deal Intelligence

A Salesforce integration uses webhooks to receive change.detected events in real time. When a pricing change is detected for a competitor involved in an active deal, the integration automatically adds a note to the opportunity in Salesforce and alerts the deal owner. Battle cards are synced nightly to custom Salesforce objects using the competitor tracking API.

Endpoints used: Webhooks, GET /competitors, GET /competitors/:id/battle_card

Bulk Competitor Onboarding

A competitive intelligence team joining RivalCheck uses a script to add 15 competitors via POST /api/v1/competitors in sequence, with a 5-second delay between each. They then monitor the GET /api/v1/account endpoint to track usage against their plan limits. After all competitors reach active status, they trigger battle card generation for each one.

Endpoints used: POST /competitors, GET /account, POST /competitors/:id/battle_card/generate

Build comparison pages on sourced facts

Start with a setup sprint or pick a paid plan, create an API key in Settings, and pull your first facts. The same key works with the MCP server.

Need help? Reach us at support@rivalcheck.com