> ## Documentation Index
> Fetch the complete documentation index at: https://docs.keywordinsights.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Public API: Keyword Discovery

> Generate keyword research from a seed keyword and retrieve enriched keyword data via the API

Discover new keyword opportunities from a seed keyword — including autocomplete suggestions, related searches, People Also Ask, and search volume metrics.

<Info>
  Full endpoint reference available on [Swagger](https://api.keywordinsights.ai/apidocs/#/Keyword%20Discovery).
</Info>

### Quick Start

Create a keyword discovery order from a seed keyword:

```python theme={null}
import os
import requests

API_KEY = os.environ["KWI_API_KEY"]
BASE_URL = "https://api.keywordinsights.ai"
HEADERS = {"X-API-Key": API_KEY}

payload = {
    "seed_keyword": "content marketing strategies",
    "language": "en",
    "location": "United States",
}

response = requests.post(
    f"{BASE_URL}/api/keyword-discovery/orders/",
    headers=HEADERS,
    json=payload,
)

data = response.json()
print(data)
# {"status": true, "payload": {"id": "339ca10b-...", "status": "created", ...}}
```

### Endpoints

| Method | Path | Description |
| - | - | - |
| POST | `/api/keyword-discovery/orders/` | Create a keyword discovery order |
| GET | `/api/keyword-discovery/orders/{order_id}/` | Get order details and processing status (polling) |
| DELETE | `/api/keyword-discovery/orders/{order_id}/` | Delete an order (owner only) |
| GET | `/api/keyword-discovery/orders/{order_id}/keywords/` | Get discovered keywords (paginated, filterable) |
| GET | `/api/keyword-discovery/orders/` | List your orders (paginated) |
| GET | `/api/keyword-discovery/languages/` | List supported languages |
| GET | `/api/keyword-discovery/locations/` | List all supported locations |
| GET | `/api/keyword-discovery/locations/{search}/` | Search locations by name (top 10 fuzzy matches) |

### Parameters

#### Create Order — Required

| Parameter | Type | Description |
| - | - | - |
| `seed_keyword` | string | The main keyword to discover related keywords for |
| `language` | string | ISO 639-1 language code (e.g. `en`). See `/api/keyword-discovery/languages/` |
| `location` | string | Location name (e.g. `United States`). See `/api/keyword-discovery/locations/` |

#### Create Order — Optional

| Parameter | Type | Description |
| - | - | - |
| `folder_id` | string | Dashboard folder ID to organize the order. Also pulls workspace/domain settings if the folder is linked to a domain |

### Polling Order Status

Orders are processed asynchronously. Poll the order endpoint until `payload.status` is `done` (or `error`). Each data source reports its own progress in `payload.processing_status`; keyword metrics (volume, CPC, competition) are attached once `processing_status.enrich_status` is `done`.

```python theme={null}
import time

def wait_for_order(order_id, poll_interval=10, max_wait=300):
    """Poll until the order finishes processing."""
    start = time.time()
    while time.time() - start < max_wait:
        response = requests.get(
            f"{BASE_URL}/api/keyword-discovery/orders/{order_id}/",
            headers=HEADERS,
        )
        response.raise_for_status()
        payload = response.json()["payload"]

        status = payload["status"]
        processing = payload.get("processing_status") or {}
        print(f"status: {status}, enrichment: {processing.get('enrich_status')}")

        if status == "error":
            raise RuntimeError("Order failed to process")
        if status == "done" and processing.get("enrich_status") == "done":
            return payload

        time.sleep(poll_interval)

    raise TimeoutError(f"Order not ready after {max_wait}s")
```

The order detail response also includes `n_keywords` (total discovered so far), a short `keywords` preview, and `seed_keyword_data` (metrics for the seed keyword itself).

| Status | Meaning |
| - | - |
| `created` | Order accepted, processing not started |
| `processing` | Keyword sources are being fetched |
| `done` | All sources fetched — check `processing_status.enrich_status` for metric enrichment |
| `error` | Processing failed (credits are refunded automatically) |

### Retrieving Keywords

Once the order is complete, retrieve discovered keywords with filtering and sorting:

```python theme={null}
order_id = "339ca10b-80c1-4248-b8d0-c11377c204c7"

response = requests.get(
    f"{BASE_URL}/api/keyword-discovery/orders/{order_id}/keywords/",
    headers=HEADERS,
    params={
        "page_size": 50,
        "page_number": 1,
        "sort_by": "volume",
        "sort_order": "desc",
    },
)

data = response.json()["payload"]
print(f"Total keywords: {data['total_list_length']}")

for kw in data["list"]:
    print(f"  {kw['keyword']} — vol: {kw['volume']}, source: {kw['source']}")
```

#### Keywords Query Parameters

**Pagination & sorting:**

| Parameter | Type | Default | Description |
| - | - | - | - |
| `page_size` | integer | 10 | Results per page (max 10000) |
| `page_number` | integer | 1 | Page number |
| `sort_by` | string | `volume` | Sort field: `keyword`, `source`, `volume`, `cpc`, `competition` |
| `sort_order` | string | `desc` | `asc` or `desc` |

**Source filtering:**

| Parameter | Type | Description |
| - | - | - |
| `source` | string | Filter by single source |
| `sources` | string\[] | Filter by multiple sources (pass as repeated query params) |

Available sources: `seed_keyword`, `google_autocomplete`, `google_related_searches`, `people_also_ask`, `quora`, `reddit`, `google_search_console`, `generated_keywords`

**Keyword filtering:**

| Parameter | Type | Description |
| - | - | - |
| `keywords_included` | string\[] | Only include keywords containing these terms |
| `keywords_excluded` | string\[] | Exclude keywords containing these terms |
| `include_hidden` | boolean | Include hidden keywords (default `false`) |

**Metric filters:**

| Parameter | Type | Description |
| - | - | - |
| `volume_from` / `volume_to` | integer | Search volume range |
| `cpc_from` / `cpc_to` | float | CPC range |
| `competition_from` / `competition_to` | float | Competition value range |
| `competition_cat` | string | Competition category: `all`, `low`, `medium`, `high` |
| `ctr_from` / `ctr_to` | float | CTR range (GSC data) |
| `clicks_from` / `clicks_to` | integer | Clicks range (GSC data) |
| `position_from` / `position_to` | float | SERP position range (GSC data) |
| `impressions_from` / `impressions_to` | integer | Impressions range (GSC data) |

### Managing Orders

#### Listing Your Orders

```python theme={null}
response = requests.get(
    f"{BASE_URL}/api/keyword-discovery/orders/",
    headers=HEADERS,
    params={
        "sort_by": "created_date",
        "sort_order": "desc",
        "search_query": "marketing",  # optional: filter by seed keyword
        "page_size": 10,
    },
)

data = response.json()["payload"]
for order in data["list"]:
    print(f"{order['seed_keyword']} — {order['status']} ({order['n_keywords']} keywords)")
```

| Parameter | Type | Default | Description |
| - | - | - | - |
| `sort_by` | string | — | `seed_keyword` or `created_date` |
| `sort_order` | string | `asc` | `asc` or `desc` |
| `search_query` | string | — | Filter orders by seed keyword |
| `page_size` | integer | 10 | Results per page |
| `page_number` | integer | 1 | Page number |

#### Deleting an Order

Only the order owner can delete an order:

```python theme={null}
response = requests.delete(
    f"{BASE_URL}/api/keyword-discovery/orders/{order_id}/",
    headers=HEADERS,
)
# {"status": true, "payload": {"status": "deleted"}, "error": null}
```

### Languages & Locations

Look up valid values for the create-order `language` and `location` fields:

```python theme={null}
# All supported languages: [{"name": "English", "code": "en"}, ...]
languages = requests.get(
    f"{BASE_URL}/api/keyword-discovery/languages/", headers=HEADERS
).json()["payload"]

# Search locations by name (top 10 fuzzy matches)
locations = requests.get(
    f"{BASE_URL}/api/keyword-discovery/locations/united/", headers=HEADERS
).json()["payload"]
# [{"name": "United States", "code": "us"}, {"name": "United Kingdom", "code": "gb"}, ...]
```

<Tip>
  The full `/api/keyword-discovery/locations/` list is large — prefer the search variant when you know the location name.
</Tip>

### Customizations

#### Filtering by Source

Narrow results to specific keyword sources:

```python theme={null}
# Only autocomplete and People Also Ask keywords
response = requests.get(
    f"{BASE_URL}/api/keyword-discovery/orders/{order_id}/keywords/",
    headers=HEADERS,
    params={
        "sources": ["google_autocomplete", "people_also_ask"],
        "sort_by": "volume",
        "sort_order": "desc",
    },
)
```

#### Filtering by Metrics

Find high-volume, low-competition keywords:

```python theme={null}
response = requests.get(
    f"{BASE_URL}/api/keyword-discovery/orders/{order_id}/keywords/",
    headers=HEADERS,
    params={
        "volume_from": 1000,
        "competition_cat": "low",
        "sort_by": "volume",
        "sort_order": "desc",
    },
)
```

### Complete Example

Create a keyword discovery order, poll until it completes, retrieve the top keywords by volume, and clean up:

```python theme={null}
import os
import time
import requests

API_KEY = os.environ["KWI_API_KEY"]
BASE_URL = "https://api.keywordinsights.ai"
HEADERS = {"X-API-Key": API_KEY}


def create_discovery(seed_keyword, language="en", location="United States"):
    """Create a keyword discovery order."""
    response = requests.post(
        f"{BASE_URL}/api/keyword-discovery/orders/",
        headers=HEADERS,
        json={
            "seed_keyword": seed_keyword,
            "language": language,
            "location": location,
        },
    )
    response.raise_for_status()
    return response.json()["payload"]


def wait_for_order(order_id, poll_interval=10, max_wait=300):
    """Poll the order endpoint until processing completes."""
    start = time.time()
    while time.time() - start < max_wait:
        response = requests.get(
            f"{BASE_URL}/api/keyword-discovery/orders/{order_id}/",
            headers=HEADERS,
        )
        response.raise_for_status()
        payload = response.json()["payload"]
        processing = payload.get("processing_status") or {}

        if payload["status"] == "error":
            raise RuntimeError("Order failed to process")
        if payload["status"] == "done" and processing.get("enrich_status") == "done":
            return payload

        time.sleep(poll_interval)
    raise TimeoutError(f"Order not ready after {max_wait}s")


def get_keywords(order_id, page_size=50, min_volume=0):
    """Fetch discovered keywords with optional volume filter."""
    response = requests.get(
        f"{BASE_URL}/api/keyword-discovery/orders/{order_id}/keywords/",
        headers=HEADERS,
        params={
            "page_size": page_size,
            "page_number": 1,
            "sort_by": "volume",
            "sort_order": "desc",
            "volume_from": min_volume,
        },
    )
    response.raise_for_status()
    return response.json()["payload"]


# --- Run ---

result = create_discovery("project management software")
order_id = result["id"]
print(f"Order created: {order_id}")

order = wait_for_order(order_id)
print(f"Processing complete: {order['n_keywords']} keywords discovered")

data = get_keywords(order_id, page_size=20, min_volume=500)
print(f"\nTop {len(data['list'])} keywords (of {data['total_list_length']} total):\n")

for kw in data["list"]:
    print(f"  {kw['keyword']:50s} vol: {kw['volume']:>6}  source: {kw['source']}")

# Optional cleanup
requests.delete(f"{BASE_URL}/api/keyword-discovery/orders/{order_id}/", headers=HEADERS)
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.