# Recursive Long-Tail Keyword Fanout Tool (`khadinakbar/keyword-fanout-longtail`) Actor

Expand seed keywords through bounded recursive Google autocomplete. Return deduplicated long-tail suggestions, seed and parent discovery paths, depth, English intent labels, locale and timestamps for content research. Bills per saved keyword plus platform usage.

- **URL**: https://apify.com/khadinakbar/keyword-fanout-longtail.md
- **Developed by:** [Khadin Akbar](https://apify.com/khadinakbar) (community)
- **Categories:** SEO tools, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.50 / 1,000 unique keyword saveds

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

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

## Keyword Fanout — Recursive Long-Tail Discovery

Turn seed phrases into a bounded graph of Google autocomplete keyword ideas. Each dataset row is a unique source-returned phrase with observed seed topics, parent paths, discovery depth, locale hints and collection time. Use it to research content angles and prepare a shortlist for separate keyword-metric analysis.

### Start with a small topic

```json
{
  "seedKeywords": ["standing desk"],
  "expansions": ["base", "questions", "comparisons", "prepositions"],
  "maxDepth": 2,
  "branchFactor": 3,
  "maxRequests": 50,
  "maxResults": 200,
  "minWords": 3,
  "language": "en",
  "country": "US"
}
```

The first level queries each seed with the selected patterns. Deeper levels query up to `branchFactor` previously unseen suggestions from each response. The frontier is traversed breadth first. At most `maxRequests` source attempts and `maxResults` unique result rows are allowed. Default depth is 2; maximum depth is 4. Source calls run sequentially, with a default 300 ms delay and a 10 second timeout. There are no automatic retries or paid provider fallbacks.

### What a row means

Every result was returned in an autocomplete response. Merely constructing `how standing desk` or another modifier query never creates a result. Case, whitespace and equivalent Unicode variants are deduplicated across seeds. The original seed phrases are excluded from results.

This abbreviated illustrative result explains the graph fields:

```json
{
  "keyword": "best standing desk for home office",
  "seedKeywords": ["standing desk"],
  "depth": 1,
  "wordCount": 7,
  "isLongTail": true,
  "intent": "commercial",
  "intentMethod": "english-rule-heuristic-v1",
  "language": "en",
  "country": "US",
  "source": "google-autocomplete",
  "discoveryPaths": [{
    "seedKeyword": "standing desk",
    "parentKeyword": "standing desk",
    "query": "best standing desk",
    "depth": 1,
    "expansion": "comparisons"
  }]
}
```

Actual rows also include a run-local stable `id`, `retrievedAt`, each path's `sourceUrl` and `retrievedAt`, and `pathsMayBeTruncated`. Up to 20 distinct observed paths are retained for each phrase. At later levels, `parentKeyword` identifies the returned phrase queried to discover the child. `seedKeywords` groups observed source relationships; it is not an inferred semantic cluster.

`intent` uses simple English rules and returns `unknown` when no rule applies or another language is selected. `wordCount` counts whitespace-separated tokens; `isLongTail` means three or more such tokens. These are transparent heuristics, not demand measurements. This Actor does not return search volume, CPC, keyword difficulty, SERP rankings or People Also Ask results.

### Workflow story

A content planner researching standing desks runs a small discovery job, groups phrases by observed seed and intent, and inspects the parent paths behind a promising angle. They export a shortlist to a volume tool before commissioning an article. A nested suggestion is evidence that the source returned that phrase, not proof of traffic or low competition.

For direct query-by-query suggestion arrays, use [Google Autocomplete Suggestions API](https://apify.com/khadinakbar/google-autocomplete-api). To measure a researched shortlist, use [Keyword Search Volume API](https://apify.com/khadinakbar/keyword-search-volume-api).

### International research and filters

Set `language` and `country` to the requested market hints. For Arabic, Urdu, Chinese or another language, select `expansions: ["base"]` and supply native-language suffixes through `customModifiers`. Built-in question, comparison and preposition patterns use English; alphabet mode uses a–z. Set `minWords: 1` for languages that do not separate words with spaces.

`excludeKeywords` applies literal case-insensitive substring filters before output and recursive scheduling. Short source phrases can still be explored to discover longer children. A separate `branchFactor` cap chooses the first eligible unseen phrases in source order, so broad coverage may require several focused runs. Seeds are processed in input order; small request caps can leave later seeds untouched.

### Output, limits and honest failures

Read `OUTPUT` and `RUN_SUMMARY` as well as the dataset. `COMPLETE` means the configured bounded graph traversal finished. `PARTIAL` means useful rows were saved but a source request failed or a request, result, deadline or charge boundary stopped work. `VALID_EMPTY` means zero delivered rows; inspect warnings to distinguish no matching suggestions from a charge cap that prevented work. Invalid inputs return `INVALID_INPUT` with no row events. If requested source work failed and no useful rows were found, the run fails with `UPSTREAM_FAILED`. Runtime or configuration failures use `CONFIG_ERROR`.

Source availability and autocomplete content vary. The undocumented native autocomplete route can change or throttle. Language and country are hints, and source order is not a search-volume ranking. Requests are bounded, not an exhaustive map of every possible phrase.

Progress is stored in the run's key-value store with an input/build fingerprint. Completed query results and delivered rows are reused on resurrection. Interrupted source attempts still consume the request budget. Dataset writes and event charges are not a transaction: an ambiguous pending delivery stops further billing and requires reconciliation against dataset and charge evidence. It is never blindly replayed.

### Pricing configuration

This is a local candidate; no live Actor price is configured yet. The draft is **$1.50 per 1,000 unique saved keyword rows, plus Apify platform usage**, with no startup event. Confirm effective event prices and usage payer in the live Pricing tab after private deployment.

Only accepted dataset rows trigger `keyword-discovered`. Duplicate suggestions, generated modifiers, filtered phrases, empty responses and failed source calls have no keyword event. Platform usage still accrues for work performed. `maxResults: 200` bounds draft keyword events to $0.30; it does not cap platform usage. Set Apify's maximum total charge separately. A low event allowance may limit delivered rows after source research; request caps remain the source-work boundary.

### API and AI-agent use

After private deployment, start a run through Apify's API with this Actor's slug and a bearer token, then read the run's default dataset and both summary records:

```bash
curl --request POST \
  'https://api.apify.com/v2/acts/khadinakbar~keyword-fanout-longtail/runs' \
  --header "Authorization: Bearer $APIFY_TOKEN" \
  --header 'Content-Type: application/json' \
  --data '{"seedKeywords":["standing desk"],"maxDepth":2,"maxRequests":20,"maxResults":50}'
```

Agent prompt: “Discover up to 50 long-tail autocomplete phrases for standing desks in US English using at most 20 requests and depth 2. Return observed parent paths, intent labels and collection times, and report any incomplete branches.”

The batch JSON interface is designed for API and hosted MCP use. Hosted MCP invocation and exposed schema remain unverified until private cloud acceptance. Limited permissions are sufficient for the Actor's own storage; no connected apps, owner-wide account access or runtime AI model is used.

### Builder's note

Keeping discovery edges makes recursive expansion auditable: a content planner can see which actual query produced a phrase. Source-returned suggestions and mechanical heuristics remain separate throughout normalization, filtering and output. Prefer a focused seed and modest caps, then inspect the graph before widening the next run.

This is an independent research tool and is not affiliated with or endorsed by Google. Deployment and publication require review of source access, data use, pricing and final-build evidence.

# Actor input Schema

## `seedKeywords` (type: `array`):

Enter 1–20 seed phrases to research. For example, use standing desk or accounting software. Equivalent Unicode, case and whitespace variants are deduplicated. Suggestions remain predictions rather than measured demand.

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

Save at most this many unique keyword rows across all seeds. Defaults to 200. Each delivered row is one keyword-discovered event. Reaching this cap may leave graph branches unexplored.

## `maxRequests` (type: `integer`):

Bound the total autocomplete attempts, including failures and interrupted attempts. Defaults to 50 with no automatic retries. Completed source queries are checkpointed. This limits source calls rather than promising an all-in platform cost.

## `maxDepth` (type: `integer`):

Explore one to four discovery levels. Level 1 uses each seed and selected research modifiers; later levels query returned phrases. Defaults to 2. A configured depth is a research boundary rather than an exhaustive-topic guarantee.

## `branchFactor` (type: `integer`):

Schedule at most this many unseen suggestions from each response for deeper exploration. Defaults to 3. Source order selects branches. Excluded suggestions never become recursive queries.

## `expansions` (type: `array`):

Select first-level query patterns: original seed, English question words, comparisons, prepositions or a–z suffixes. Later levels use discovered phrases directly. For other languages choose base and add your own modifiers. Modifier queries become output only when returned by the source.

## `language` (type: `string`):

Set the autocomplete language hint, such as en, ar or pt-BR. Defaults to en. This does not translate research modifiers. English intent rules return unknown for other languages.

## `country` (type: `string`):

Set a two-letter market hint such as US, GB or PK. Defaults to US. Google may also use the request location and current context. A country hint does not guarantee local-origin suggestions.

## `minWords` (type: `integer`):

Keep returned phrases with at least this many whitespace-separated words. Defaults to 3. Use 1 for languages that do not commonly separate words with spaces. isLongTail is a three-word heuristic and does not measure competition.

## `customModifiers` (type: `array`):

Append up to 20 literal suffixes to each seed at level 1. For example, add for small apartments. Use native-language suffixes for international research. These queries consume the same request cap.

## `excludeKeywords` (type: `array`):

Exclude suggestions containing any of these literal case-insensitive fragments. Excluded phrases are neither returned nor recursively queried. Up to 50 fragments are accepted. This uses substring matching rather than regular expressions.

## `requestDelayMillis` (type: `integer`):

Wait this many milliseconds before each sequential source call. Defaults to 300 and cannot be below 100. Higher delays can reduce source pressure. This does not bypass throttling or guarantee source availability.

## `requestTimeoutSecs` (type: `integer`):

Limit each source request, including its response body, to this many seconds. Defaults to 10. Failed attempts consume maxRequests. No paid provider fallback is invoked.

## Actor input object example

```json
{
  "seedKeywords": [
    "standing desk"
  ],
  "maxResults": 200,
  "maxRequests": 50,
  "maxDepth": 2,
  "branchFactor": 3,
  "expansions": [
    "base",
    "questions",
    "comparisons",
    "prepositions"
  ],
  "language": "en",
  "country": "US",
  "minWords": 3,
  "customModifiers": [],
  "excludeKeywords": [],
  "requestDelayMillis": 300,
  "requestTimeoutSecs": 10
}
```

# Actor output Schema

## `keywords` (type: `string`):

Deduplicated suggestions with graph paths, source, locale and English intent heuristic.

## `output` (type: `string`):

OUTPUT contains the terminal outcome and confirmed saved and charged counts.

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

RUN_SUMMARY includes caps, requests, failures and any unresolved delivery.

# 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 = {
    "seedKeywords": [
        "standing desk"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("khadinakbar/keyword-fanout-longtail").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 = { "seedKeywords": ["standing desk"] }

# Run the Actor and wait for it to finish
run = client.actor("khadinakbar/keyword-fanout-longtail").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 '{
  "seedKeywords": [
    "standing desk"
  ]
}' |
apify call khadinakbar/keyword-fanout-longtail --silent --output-dataset

```

## MCP server setup

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

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/792ddjbMQ20dG2u0D/builds/ihn8IEZGP9RfOgjoB/openapi.json
