# SaaS Pricing, Seat Costs & Limits Monitor (`thescrapelab/saas-pricing-limits-monitor`) Actor

Compare public SaaS plans, estimate team seat costs, and monitor published price and limit changes with source evidence.

- **URL**: https://apify.com/thescrapelab/saas-pricing-limits-monitor.md
- **Developed by:** [Inus Grobler](https://apify.com/thescrapelab) (community)
- **Categories:** Automation, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$3.00 / 1,000 successful pricing page checks

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

### Monitor SaaS pricing and calculate published team costs

**SaaS Pricing, Seat Costs & Limits Monitor** reads public pricing pages from Linear, Notion, GitHub, Figma, and Slack. It returns source-linked plan rates, stated limits, and an estimate for your team size. Run the same pages again to see whether a published price or selected limit changed.

This Actor is useful for product marketers comparing competitors, procurement teams reviewing software spend, and founders watching how similar products package their plans. It extracts public information without a source API key or a paid data provider.

### Use cases

- **Competitor pricing research:** compare public monthly and annual plan rates with the wording that supports each rate.
- **Software budget estimates:** apply your team size, or Figma seat mix, to published per-seat base prices.
- **Pricing change monitoring:** repeat a watchlist to see rate and stated limit changes with previous and current values.

### Start in three steps

1. Enter one or more supported public pricing-page URLs.
2. Set **Team seats**. If you include Figma, optionally set its Full, Dev, and Collab seat counts.
3. Run the Actor. The first successful check creates a baseline; later runs using the same **History name** show changes.

### Input

Example input:

```json
{
  "pricingUrls": ["https://linear.app/pricing", "https://www.figma.com/pricing/"],
  "teamSize": 25,
  "seatMix": { "full": 10, "dev": 10, "collab": 5 }
}
```

Use the same History name for repeated checks of a watchlist. It must contain lowercase letters, digits, and hyphens between characters; underscores are not accepted by Apify storage. Give independent watchlists different names. Avoid overlapping runs that use the same name. You can schedule repeat runs in Apify Console when you need ongoing monitoring; the Actor does not create a schedule for you.

### Output

The default dataset has one row per requested page. Each supported plan contains its published per-seat monthly rate, whether the customer pays monthly or commits annually, its seat class, short source wording, selected stated limits, and calculated team cost where enough information is available. Annual commitment is 12 times the published monthly equivalent for annually billed offers. Results can be exported from Apify as JSON, CSV, or Excel. If no requested page produces a valid pricing result, the run fails; its diagnostic rows and `RUN_SUMMARY` remain available.

Simplified example from a Linear page, for 25 seats:

```json
{
  "url": "https://linear.app/pricing",
  "vendor": "linear",
  "status": "ok",
  "currencyCode": null,
  "currencySymbol": "$",
  "changeType": "baseline",
  "plans": [{
    "name": "Basic",
    "kind": "fixed",
    "prices": [{
      "amount": "10.00",
      "billing": "annual",
      "seatType": "standard",
      "qualifier": "standard",
      "evidence": "Basic $10 per user/month",
      "estimatedMonthlyCost": "250.00",
      "estimatedAnnualCommitment": "3000.00"
    }],
    "limits": ["5 teams"]
  }]
}
```

The example is illustrative; live prices can change. A dollar sign alone does not prove a currency code, so `currencyCode` remains `null` unless the page explicitly identifies USD. The `RUN_SUMMARY` output reports counts by check status and the number of detected changes.

### How price and limit monitoring works

The Actor compares normalized published rates, billing terms, seat classes, and selected stated limits with the last **reliably parsed** snapshot. It reports `baseline`, `unchanged`, `price_change`, or `packaging_change`. Each change includes its type, affected plan and field, previous value, and current value. If a page blocks access, a required rate disappears, or its layout changes, the row explains the failure and the previous good snapshot remains available for the next check. A changed plan lineup may require an adapter update before monitoring can resume.

Figma prices are separated into Full, Dev, and Collab seats. A Figma team estimate stays empty until you provide a seat mix. GitHub rates labelled as introductory or “starting at” retain those qualifiers. Slack's temporary promotional rates do not replace its displayed standard monthly rate in the team estimate. The Actor does not treat a promotion as an ongoing price.

### Supported pages and limits

- Supported v1 pages: `linear.app/pricing`, `notion.com/pricing`, `github.com/pricing`, `figma.com/pricing/`, and `slack.com/pricing` on their public English layouts. Other URLs receive `unsupported` rather than invented plan data.
- Prices are published base rates. They exclude tax, usage overages, discounts not shown in the pricing card, currency conversion, custom quotes, and eligibility checks. A `starting_at` or introductory estimate applies only to the published qualifying rate.
- Stated limits are extracted claims, not independently verified entitlements. Pages may change by region, currency, account, or time; review the linked source before making a purchase decision.
- The Actor respects source robots rules, does not log in, and does not bypass challenges. A blocked or changed layout produces an error row instead of a guessed rate.

### How much does a run cost?

Introductory pay-per-event price: **$0.003 per successfully checked supported pricing page** ($3 per 1,000 checks). A five-page watchlist costs at most $0.015 in Actor events per run. Baseline checks and unchanged repeat checks both count: each delivers a fresh validated pricing snapshot. Unsupported, blocked, and failed pages have no Actor event charge. There is no run-start fee, and platform usage is included in the event price. Apify free-plan users can try the Actor under Apify's free-plan rules. Set a maximum charge per run in Console or the API to limit the number of paid page results; pages skipped because the charge limit was reached receive a diagnostic row and do not update monitoring history.

Browser interaction is used for pages with monthly/annual controls; GitHub and Slack normally use lighter HTML requests. The default 2 GB memory setting has headroom for a complete five-vendor run; one page at a time is the default for predictable memory use. Start with one page before running a larger watchlist.

### Use the Apify API from Python

```python
import os
from apify_client import ApifyClient

client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("thescrapelab/saas-pricing-limits-monitor").call(
    run_input={
        "pricingUrls": ["https://linear.app/pricing"],
        "teamSize": 25,
    }
)
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["vendor"], item["status"], item["changeType"])
```

If a supported page repeatedly returns `LAYOUT_DRIFT` or `BLOCKED`, include the vendor, run ID, and error code when contacting the developer through Apify Console. Do not include private account details or tokens in a support report.

# Changelog

This Actor's version history is a separate document: https://apify.com/thescrapelab/saas-pricing-limits-monitor/changelog.md

# Actor input Schema

## `pricingUrls` (type: `array`):

Public HTTPS pricing pages. Supported v1 layouts: Linear, Notion, GitHub, Figma, and Slack. Other pages return an unsupported status rather than guessed prices.

## `teamSize` (type: `integer`):

Seats used to estimate published per-user plan costs. Figma's distinct seat types use the seat mix below instead.

## `seatMix` (type: `object`):

Optional seat counts for Figma. Leave empty to extract prices without calculating a Figma total.

## `historyStoreName` (type: `string`):

Named history shared by repeat runs in your account. Use lowercase letters, digits, and middle hyphens only. Use another name for an independent watchlist. Do not overlap runs using the same name.

## `maxConcurrency` (type: `integer`):

Maximum pages processed at once. Keep low for respectful access and browser memory.

## Actor input object example

```json
{
  "pricingUrls": [
    "https://linear.app/pricing"
  ],
  "teamSize": 25,
  "historyStoreName": "saas-pricing-limits-monitor-v1",
  "maxConcurrency": 1
}
```

# Actor output Schema

## `results` (type: `string`):

No description

## `runSummary` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "pricingUrls": [
        "https://linear.app/pricing"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("thescrapelab/saas-pricing-limits-monitor").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "pricingUrls": ["https://linear.app/pricing"] }

# Run the Actor and wait for it to finish
run = client.actor("thescrapelab/saas-pricing-limits-monitor").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "pricingUrls": [
    "https://linear.app/pricing"
  ]
}' |
apify call thescrapelab/saas-pricing-limits-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,thescrapelab/saas-pricing-limits-monitor"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/44UX9sEtFuRD1xSXu/builds/yH58XXRlzKWvKr8ZF/openapi.json
