# SwitchSignal - Competitor Switching Intent Leads (`neoscala/switchsignal`) Actor

Find public competitor pain signals, score switching intent, and output sales-ready contact candidates with evidence and outreach angles.

- **URL**: https://apify.com/neoscala/switchsignal.md
- **Developed by:** [Neo Scala](https://apify.com/neoscala) (community)
- **Categories:** Lead generation, AI, Agents
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2,000.00 / 1,000 qualified signals

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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## SwitchSignal

**Find public competitor switching-intent signals and turn them into sales-ready contact candidates.**

SwitchSignal helps sales and growth teams find public discussions where people appear to be unhappy with a competitor, considering alternatives, discussing migration, or showing signs of switching intent.

It is designed for sales users, founders, SDRs, growth teams and consultants who want a simple way to spot competitor-displacement opportunities without manually searching public communities.

***

### What SwitchSignal does

You enter a competitor name, choose a public source, and run the Actor.

SwitchSignal then:

1. Searches supported public sources.
2. Filters out obvious noise, vendor promotion and ordinary word matches.
3. Scores commercial intent.
4. Classifies the lead type.
5. Generates recommended next action.
6. Returns contact candidates and company clues where public evidence exists.
7. Writes clean results to the Apify Dataset.

***

### Typical use cases

- Find public posts where people are looking for alternatives to a competitor.
- Monitor pricing pain around a competitor.
- Spot migration or replacement discussions.
- Discover competitor-displacement opportunities.
- Build a weekly list of public contact candidates to manually research.

***

### Important: what counts as a lead?

SwitchSignal is strict by design.

It should qualify buyer-side signals such as:

- “We are looking for an Intercom alternative.”
- “We are moving away from Heroku because the bill is too high.”
- “Support has been painful and we need to replace Zendesk.”

It should reject non-buyer signals such as:

- “I built an Intercom alternative.”
- “Our product is an alternative to Monday.”
- “Monday” used as a day of the week.
- “The notion of privacy” where Notion is only an ordinary English word.
- A company name appearing in a list of layoffs or news items.
- “We considered replacing HubSpot, but HubSpot will be kept.”

***

### Current supported sources

| Source | Status | Notes |
|---|---|---|
| Hacker News | Supported | Best current automated source for SaaS, developer tools and infrastructure discussions. |
| Public URLs | Supported | Paste a public URL and optional snippet for scoring and AI enrichment. |
| Reddit user-provided URLs/text | Supported fallback | Full automated Reddit discovery may require approved Reddit API access. |
| Reddit search-index snippets | Supported fallback | Score externally supplied Reddit URLs, titles and snippets. |
| LinkedIn | Not native in v1 | Can be considered later through approved APIs, imports or external Apify sources. |
| X, G2, Capterra, Trustpilot | Not native in v1 | Candidate future sources through approved or licensed access. |

***

### Simple setup

#### 1. Enter the competitor

Example:

```text
Intercom
```

#### 2. Add aliases

Example:

```text
Intercom
intercom.com
Intercom support
```

#### 3. Describe your product

Example:

```text
A simpler customer support platform for SaaS teams.
```

#### 4. Choose a source

Start with:

```text
Hacker News
```

#### 5. Choose result limit and threshold

Recommended beta settings:

```text
Maximum results: 10
Minimum intent score: 60
AI enrichment: off for quality testing, on for better summaries and openers
```

***

### Output fields explained in plain English

| Field | Meaning |
|---|---|
| `intentScore` | How strong the switching-intent signal is, from 0 to 100. |
| `leadType` | Whether the record looks like buyer intent, vendor promotion, noise, etc. |
| `commercialIntent` | Business relevance: none, low, medium, high or very high. |
| `salesReadiness` | What to do next: ignore, monitor, research company, or reach out now. |
| `priorityTier` | Critical, high, medium or low. |
| `qualificationReason` | Why SwitchSignal qualified or rejected the signal. |
| `sourceUrl` | Link to the original public discussion. |
| `authorHandle` | Public handle, for example Hacker News username. |
| `companyCandidate` | Possible company clue, not a verified company. |
| `companyConfidence` | Confidence in the company clue. |
| `suggestedOpener` | A suggested first line for outreach, human review required. |

***

### Key outputs

SwitchSignal writes qualified signals to the default Dataset.

It also writes these summaries to the default Key-value Store:

| Record | What it means |
|---|---|
| `RUN_SUMMARY` | Overall counts, filters, qualified signals and warnings. |
| `SOURCE_DIAGNOSTICS` | Source-level status and warnings. |
| `AI_ENRICHMENT_SUMMARY` | AI usage summary, success, failure and skipped counts. |
| `CONTACT_SUMMARY` | Deduplicated public contact candidates and company clues. |

***

### Contact candidates are not verified contacts

SwitchSignal does **not** provide verified emails, phone numbers or guaranteed company identification in v1.

It returns public contact candidates and company clues.

Always review before outreach.

Company clues can be:

- profile-domain evidence,
- source-domain evidence,
- public author handle evidence,
- or unknown.

Low-confidence company clues should be manually checked before contacting anyone.

***

### AI enrichment

AI enrichment is optional.

When enabled, SwitchSignal can improve:

- summary,
- evidence,
- suggested opener,
- intent interpretation.

You can either:

- use the Actor owner’s configured OpenAI key where available, or
- provide your own OpenAI Platform key in the secret input field.

If AI is not available, SwitchSignal still works in rules-only mode.

***

### Scheduling and email notifications

SwitchSignal can be run on a schedule using Apify schedules.

Email notifications are not built directly into SwitchSignal v1, but can be set up using Apify webhooks and an automation tool such as Make, Zapier, n8n, SendGrid or Gmail automation.

Simple flow:

```text
Apify Schedule
→ Run SwitchSignal daily or weekly
→ Apify Webhook after successful run
→ Automation checks whether qualifiedSignals > 0
→ Send email digest
```

See:

- `docs/scheduled-monitoring.md`
- `docs/email-notifications.md`

***

### Recommended beta settings

For first-time users:

```text
Source: Hacker News
Time window: last 30 days
Maximum results: 10
Minimum intent score: 60
AI enrichment: off for quality testing, on for polished summaries
```

For a simple deterministic test:

```text
Source: Public URLs
AI enrichment: off
Use supplied snippet
```

***

### Pricing note

SwitchSignal is priced per qualified signal, not per raw crawled page.

A qualified signal means SwitchSignal believes the record passed its buyer-intent quality gates and was written to the Dataset.

During beta, users should manually review results before acting on them.

***

### Limitations

SwitchSignal is a paid beta product.

Current limitations:

- It may return zero results for some competitors or sources.
- It may miss relevant signals that do not use explicit switching language.
- Company clues are not guaranteed.
- Reddit full discovery may require approved API access.
- LinkedIn is not a native v1 source.
- AI-generated openers should be reviewed before use.

***

### Responsible use

Use SwitchSignal for respectful, human-reviewed sales research.

Do not use it for spam, harassment, automated mass messaging, scraping private communities or bypassing platform rules.

***

### Quick beta test question

After running SwitchSignal, ask yourself:

```text
Would I trust this result enough to research or contact this person?
```

If the answer is no, record the reason and use it as feedback.

# Actor input Schema

## `competitorName` (type: `string`):

The main competitor to monitor, for example Intercom, Zendesk, HubSpot, Salesforce, ClickUp, Monday, Notion, Mailchimp, or a Shopify app.

## `competitorAliases` (type: `array`):

Alternative spellings, domains, product names, or common ways people mention the competitor.

## `productCategory` (type: `string`):

The category your product competes in. This helps classify whether a complaint is commercially relevant.

## `yourProductDescription` (type: `string`):

Short description of your product or positioning. Used to create more relevant outreach openers.

## `sources` (type: `array`):

Choose which sources SwitchSignal should search.

## `targetUrls` (type: `array`):

Optional specific public URLs to crawl when the Specific public URLs source is selected. This is not an open-ended web crawler.

## `publicUrlSnippets` (type: `array`):

Optional copied page text or search-result snippets paired with targetUrls by position. If supplied, SwitchSignal scores the snippet even when the live page cannot be fetched.

## `allowedDomains` (type: `array`):

Optional domain allowlist for public URL crawling. Reddit and Hacker News are handled by their own collectors.

## `maxPublicUrlBytes` (type: `integer`):

Safety limit for each fetched public page before text extraction.

## `daysBack` (type: `integer`):

Only return posts from this many days back where a source provides dates.

## `maxResults` (type: `integer`):

Maximum number of qualified switch signals to write to the dataset.

## `maxItemsPerSource` (type: `integer`):

Safety limit before filtering and scoring.

## `minIntentScore` (type: `integer`):

Only output records with this score or higher.

## `includeComments` (type: `boolean`):

When supported by the source, include comments as candidate signals, not only top-level posts.

## `maxCommentsPerThread` (type: `integer`):

Limits how many comments we inspect from each thread.

## `extractPublicContactEmails` (type: `boolean`):

Disabled by default. When enabled, only extracts email addresses visibly present in the crawled public text. It does not verify, enrich, guess, or search for emails.

## `enableContactResearch` (type: `boolean`):

Fetch lightweight public profile metadata where a supported source exposes it, for example Hacker News user profile about text and links. This does not verify identities or enrich private contact data.

## `maxProfilesToResearch` (type: `integer`):

Safety limit for source profile lookups such as Hacker News user profiles.

## `enableAiEnrichment` (type: `boolean`):

Use OpenAI to improve summaries, categories, evidence, confidence and outreach openers. If no API key is supplied, the Actor falls back to rules-only output.

## `aiProvider` (type: `string`):

AI provider for enrichment. Use OpenAI Platform for v1. Select Rules-only to disable external AI calls even if AI enrichment is enabled.

## `openAiApiKey` (type: `string`):

Optional OpenAI Platform API key. Use a project or service-account key from platform.openai.com, not an Azure AI Foundry key. Stored as a secret input field. If empty, the Actor uses rules-only mode unless OPENAI\_API\_KEY is set as a secret environment variable.

## `aiModel` (type: `string`):

OpenAI Platform model used for optional AI enrichment. For Apify Store v1, keep this as an OpenAI model name, not an Azure deployment name.

## `redditMode` (type: `string`):

How the Reddit source should run. Auto uses approved API credentials if available, otherwise falls back to user-provided Reddit URLs/text when supplied. Search index and external Actor modes are explicit fallback/connector modes.

## `redditClientId` (type: `string`):

Optional Reddit API client ID. If empty, the Actor can read REDDIT\_CLIENT\_ID from environment variables.

## `redditClientSecret` (type: `string`):

Optional Reddit API client secret. If empty, the Actor can read REDDIT\_CLIENT\_SECRET from environment variables.

## `redditUserAgent` (type: `string`):

Optional Reddit API user agent, for example: nodejs:SwitchSignal:v1.0.0 (by /u/your\_username).

## `allowRedditPublicJsonFallback` (type: `boolean`):

Disabled by default. This legacy unauthenticated mode is useful only for testing and can be blocked with HTTP 403. Do not rely on it for commercial runs.

## `redditSeedUrls` (type: `array`):

Optional Reddit thread/comment URLs supplied by the user or another discovery process. Used by the user-provided and search-index fallback modes.

## `redditSeedTexts` (type: `array`):

Optional copied Reddit post/comment text or search-result snippets. Items are paired with redditSeedUrls by position. This allows SwitchSignal to score Reddit discussions without direct Reddit API access.

## `redditSearchIndexQueries` (type: `array`):

Optional search-index queries such as site:reddit.com/r/ "Intercom alternative". SwitchSignal records these as diagnostics and can score supplied search-index result URLs/titles/snippets.

## `redditSearchIndexResultUrls` (type: `array`):

Optional Reddit result URLs discovered outside the Actor, for example from a search index query. Items are paired by position with result titles and snippets below.

## `redditSearchIndexResultTitles` (type: `array`):

Optional titles for Reddit search-index results, paired by position with redditSearchIndexResultUrls.

## `redditSearchIndexResultSnippets` (type: `array`):

Optional snippets copied from search-index results, paired by position with redditSearchIndexResultUrls. This is scored without directly crawling Reddit.

## `redditExternalActorId` (type: `string`):

Optional. Future connector setting for calling a third-party or internal Reddit Actor. SwitchSignal validates the mode and warns if this is selected without a configured integration.

## `redditExternalActorInputJson` (type: `string`):

Optional JSON payload for a future external Reddit Actor connector. Leave empty unless the external Actor integration is configured.

## `debugMode` (type: `boolean`):

Write extra diagnostics to logs and the run summary. Do not enable for normal paid runs.

## `aiMaxSignals` (type: `integer`):

Cost safeguard. Only the top N qualified signals are sent for AI enrichment.

## `aiMinimumRulesScore` (type: `integer`):

Only qualified signals with at least this rules-based score are sent for AI enrichment.

## `aiTimeoutMs` (type: `integer`):

Timeout per AI enrichment request.

## `aiMaxTextChars` (type: `integer`):

Privacy and cost safeguard. Long posts are truncated before AI enrichment.

## Actor input object example

```json
{
  "competitorName": "Intercom",
  "competitorAliases": [
    "Intercom",
    "intercom.com",
    "Intercom support"
  ],
  "productCategory": "customer support software",
  "yourProductDescription": "A simpler customer support platform for growing SaaS teams.",
  "sources": [
    "hackerNews"
  ],
  "targetUrls": [],
  "publicUrlSnippets": [],
  "allowedDomains": [
    "reddit.com",
    "news.ycombinator.com",
    "hn.algolia.com"
  ],
  "maxPublicUrlBytes": 250000,
  "daysBack": 90,
  "maxResults": 100,
  "maxItemsPerSource": 150,
  "minIntentScore": 60,
  "includeComments": true,
  "maxCommentsPerThread": 25,
  "extractPublicContactEmails": false,
  "enableContactResearch": true,
  "maxProfilesToResearch": 25,
  "enableAiEnrichment": true,
  "aiProvider": "openai",
  "aiModel": "gpt-5.4-mini",
  "redditMode": "auto",
  "redditUserAgent": "nodejs:SwitchSignal:v1.0.0 (by /u/your_reddit_username)",
  "allowRedditPublicJsonFallback": false,
  "redditSeedUrls": [
    "https://www.reddit.com/r/SaaS/comments/example/intercom_alternative_discussion/"
  ],
  "redditSeedTexts": [
    "Intercom has become too expensive and support is slow. Looking for alternatives."
  ],
  "redditSearchIndexQueries": [
    "site:reddit.com/r/ \"Intercom alternative\"",
    "site:reddit.com/r/ \"Intercom too expensive\""
  ],
  "redditSearchIndexResultUrls": [
    "https://www.reddit.com/r/SaaS/comments/example/intercom_alternative_discussion/"
  ],
  "redditSearchIndexResultTitles": [
    "Intercom alternatives before renewal"
  ],
  "redditSearchIndexResultSnippets": [
    "Intercom has become too expensive and support has been painful. We are looking for alternatives before renewal."
  ],
  "debugMode": false,
  "aiMaxSignals": 25,
  "aiMinimumRulesScore": 60,
  "aiTimeoutMs": 30000,
  "aiMaxTextChars": 3500
}
```

# Actor output Schema

## `signals` (type: `string`):

Dataset records containing competitor complaint signals, scores, evidence, and outreach openers.

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

Summary of source counts, filtering counts, scoring thresholds, and diagnostics.

## `sourceDiagnostics` (type: `string`):

Per-source connector status, duration, raw item counts, warnings, errors, and source metadata.

## `aiEnrichmentSummary` (type: `string`):

Counts, warnings and errors for optional AI enrichment. Present even when AI is disabled or no API key is supplied.

## `contactSummary` (type: `string`):

Deduplicated public contact candidates, company candidates, research status and the source URLs that support each contact.

# 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 = {
    "competitorName": "Intercom",
    "competitorAliases": [
        "Intercom",
        "intercom.com",
        "Intercom support"
    ],
    "productCategory": "customer support software",
    "yourProductDescription": "A simpler customer support platform for growing SaaS teams.",
    "targetUrls": [],
    "publicUrlSnippets": [],
    "allowedDomains": [
        "reddit.com",
        "news.ycombinator.com",
        "hn.algolia.com"
    ],
    "redditUserAgent": "nodejs:SwitchSignal:v1.0.0 (by /u/your_reddit_username)",
    "redditSeedUrls": [
        "https://www.reddit.com/r/SaaS/comments/example/intercom_alternative_discussion/"
    ],
    "redditSeedTexts": [
        "Intercom has become too expensive and support is slow. Looking for alternatives."
    ],
    "redditSearchIndexQueries": [
        "site:reddit.com/r/ \"Intercom alternative\"",
        "site:reddit.com/r/ \"Intercom too expensive\""
    ],
    "redditSearchIndexResultUrls": [
        "https://www.reddit.com/r/SaaS/comments/example/intercom_alternative_discussion/"
    ],
    "redditSearchIndexResultTitles": [
        "Intercom alternatives before renewal"
    ],
    "redditSearchIndexResultSnippets": [
        "Intercom has become too expensive and support has been painful. We are looking for alternatives before renewal."
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("neoscala/switchsignal").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 = {
    "competitorName": "Intercom",
    "competitorAliases": [
        "Intercom",
        "intercom.com",
        "Intercom support",
    ],
    "productCategory": "customer support software",
    "yourProductDescription": "A simpler customer support platform for growing SaaS teams.",
    "targetUrls": [],
    "publicUrlSnippets": [],
    "allowedDomains": [
        "reddit.com",
        "news.ycombinator.com",
        "hn.algolia.com",
    ],
    "redditUserAgent": "nodejs:SwitchSignal:v1.0.0 (by /u/your_reddit_username)",
    "redditSeedUrls": ["https://www.reddit.com/r/SaaS/comments/example/intercom_alternative_discussion/"],
    "redditSeedTexts": ["Intercom has become too expensive and support is slow. Looking for alternatives."],
    "redditSearchIndexQueries": [
        "site:reddit.com/r/ \"Intercom alternative\"",
        "site:reddit.com/r/ \"Intercom too expensive\"",
    ],
    "redditSearchIndexResultUrls": ["https://www.reddit.com/r/SaaS/comments/example/intercom_alternative_discussion/"],
    "redditSearchIndexResultTitles": ["Intercom alternatives before renewal"],
    "redditSearchIndexResultSnippets": ["Intercom has become too expensive and support has been painful. We are looking for alternatives before renewal."],
}

# Run the Actor and wait for it to finish
run = client.actor("neoscala/switchsignal").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 '{
  "competitorName": "Intercom",
  "competitorAliases": [
    "Intercom",
    "intercom.com",
    "Intercom support"
  ],
  "productCategory": "customer support software",
  "yourProductDescription": "A simpler customer support platform for growing SaaS teams.",
  "targetUrls": [],
  "publicUrlSnippets": [],
  "allowedDomains": [
    "reddit.com",
    "news.ycombinator.com",
    "hn.algolia.com"
  ],
  "redditUserAgent": "nodejs:SwitchSignal:v1.0.0 (by /u/your_reddit_username)",
  "redditSeedUrls": [
    "https://www.reddit.com/r/SaaS/comments/example/intercom_alternative_discussion/"
  ],
  "redditSeedTexts": [
    "Intercom has become too expensive and support is slow. Looking for alternatives."
  ],
  "redditSearchIndexQueries": [
    "site:reddit.com/r/ \\"Intercom alternative\\"",
    "site:reddit.com/r/ \\"Intercom too expensive\\""
  ],
  "redditSearchIndexResultUrls": [
    "https://www.reddit.com/r/SaaS/comments/example/intercom_alternative_discussion/"
  ],
  "redditSearchIndexResultTitles": [
    "Intercom alternatives before renewal"
  ],
  "redditSearchIndexResultSnippets": [
    "Intercom has become too expensive and support has been painful. We are looking for alternatives before renewal."
  ]
}' |
apify call neoscala/switchsignal --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,neoscala/switchsignal"
        }
    }
}

```

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/ZTzPUCQSMgwGK1bHo/builds/NPUteQl694dlSox9h/openapi.json
