> ## 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: Clustering

> Create keyword clustering orders, check status, and retrieve results via the API

Cluster keywords by SERP similarity, detect search intent, and track rankings — all programmatically.

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

### Quick Start

Create a clustering order with the minimum required parameters:

<CodeGroup>
  ```python 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 = {
      "project_name": "My first cluster",
      "keywords": ["keyword clustering", "keyword grouping", "cluster keywords"],
      "search_volumes": [2300, 1200, 900],
      "language": "en",
      "location": "United States",
      "insights": ["cluster", "context"],
      "folder_id": "<your_folder_id>",
  }

  response = requests.post(
      f"{BASE_URL}/api/keywords-insights/order/",
      headers=HEADERS,
      json=payload,
  )

  data = response.json()
  print(data)
  # {"status": true, "order_id": "2879fa0b-...", "cost": 150}
  ```

  ```javascript JavaScript theme={null}
  const API_KEY = process.env.KWI_API_KEY;
  const BASE_URL = "https://api.keywordinsights.ai";
  const HEADERS = {
    "X-API-Key": API_KEY,
    "Content-Type": "application/json",
  };

  const payload = {
    project_name: "My first cluster",
    keywords: ["keyword clustering", "keyword grouping", "cluster keywords"],
    search_volumes: [2300, 1200, 900],
    language: "en",
    location: "United States",
    insights: ["cluster", "context"],
    folder_id: "<your_folder_id>",
  };

  async function createOrder() {
    const response = await fetch(`${BASE_URL}/api/keywords-insights/order/`, {
      method: "POST",
      headers: HEADERS,
      body: JSON.stringify(payload),
    });

    const data = await response.json();
    console.log(data);
  }

  createOrder();
  ```
</CodeGroup>

### Endpoints

| Method | Path                                                                 | Description                     |
| ------ | -------------------------------------------------------------------- | ------------------------------- |
| POST   | `/api/keywords-insights/order/`                                      | Create a clustering order       |
| GET    | `/api/keywords-insights/order/?order_id={id}`                        | Check order status              |
| GET    | `/api/keywords-insights/order/json/{order_id}/`                      | Get results as JSON (paginated) |
| GET    | `/api/keywords-insights/order/xlsx/{order_id}/`                      | Export results as XLSX          |
| GET    | `/api/keywords-insights/orders/?n_orders={n}`                        | List recent orders              |
| GET    | `/api/keywords-insights/order/cost/?n_keywords={n}&insights=cluster` | Estimate cost                   |
| GET    | `/api/keywords-insights/languages/`                                  | Supported languages             |
| GET    | `/api/keywords-insights/locations/`                                  | Supported locations             |

### Parameters

#### Required

| Parameter        | Type      | Description                                                                                      |
| ---------------- | --------- | ------------------------------------------------------------------------------------------------ |
| `project_name`   | string    | Name shown in the dashboard                                                                      |
| `keywords`       | string\[] | List of keywords to cluster (min 5, max 200,000)                                                 |
| `search_volumes` | number\[] | Search volume per keyword (same length as `keywords`)                                            |
| `language`       | string    | Language code (e.g. `en`). See `/api/keywords-insights/languages/`                               |
| `location`       | string    | Location name (e.g. `United States`). See `/api/keywords-insights/locations/`                    |
| `insights`       | string\[] | Insight types: `cluster`, `context`, `rank`                                                      |
| `folder_id`      | string    | Dashboard folder ID to save the project into. Retrieve from the browser URL in the KWI dashboard |

#### Optional

| Parameter             | Type    | Default   | Description                                                       |
| --------------------- | ------- | --------- | ----------------------------------------------------------------- |
| `clustering_method`   | string  | `volume`  | `volume` or `agglomerative`                                       |
| `grouping_accuracy`   | integer | `4`       | SERP overlap threshold (1–7). Higher = stricter clusters          |
| `hub_creation_method` | string  | `medium`  | Topical cluster similarity: `soft`, `medium`, `hard`              |
| `device`              | string  | `desktop` | SERP device: `desktop`, `mobile`, `tablet`                        |
| `mobile_type`         | string  | —         | `iphone` or `android` (when `device` is `mobile`)                 |
| `tablet_type`         | string  | —         | `ipad` or `android` (when `device` is `tablet`)                   |
| `url`                 | string  | —         | Domain URL for ranking. **Required** when `rank` is in `insights` |

### Insight Types

The `insights` array controls what analysis runs:

* **`cluster`** — Groups keywords by SERP similarity. **Required** for all clustering orders.
* **`context`** — Detects search intent (informational, commercial, transactional, navigational).
* **`rank`** — Tracks your domain's ranking for each keyword. Requires the `url` parameter.

**Full clustering order:**

```json theme={null}
"insights": ["cluster", "rank", "context"]
```

**Intent-only order** (no clustering, just intent classification):

```json theme={null}
"insights": ["context"]
```

### Customizations

#### Clustering Method

* **`volume`** (default) — Groups keywords based on shared SERP URLs. Controlled by `grouping_accuracy` (1 = loose, 7 = strict). Best for most use cases.
* **`agglomerative`** — Uses hierarchical clustering for larger keyword sets. Better for discovering broader topic relationships.

#### Topical Clusters

The `hub_creation_method` controls how tightly related keywords must be to form a topical cluster:

* **`soft`** — Broad grouping, more keywords per topical cluster
* **`medium`** — Balanced (recommended)
* **`hard`** — Strict grouping, fewer but more focused clusters

### Checking Order Status

Orders process asynchronously. Poll the status endpoint until `status` is `"done"`:

<CodeGroup>
  ```python 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}

  order_id = "2879fa0b-deae-4aab-ad87-fd8fb8db9717"

  response = requests.get(
      f"{BASE_URL}/api/keywords-insights/order/",
      headers=HEADERS,
      params={"order_id": order_id},
  )

  data = response.json()
  print(data["status"])    # "confirmed", "processing", or "done"
  print(data["progress"])  # 0.0 to 1.0
  ```

  ```javascript JavaScript theme={null}
  const API_KEY = process.env.KWI_API_KEY;
  const BASE_URL = "https://api.keywordinsights.ai";
  const HEADERS = { "X-API-Key": API_KEY };

  const orderId = "2879fa0b-deae-4aab-ad87-fd8fb8db9717";

  async function checkStatus() {
    const url = new URL(`${BASE_URL}/api/keywords-insights/order/`);
    url.searchParams.append("order_id", orderId);

    const response = await fetch(url, { headers: HEADERS });
    const data = await response.json();

    console.log(data.status);   // "confirmed", "processing", or "done"
    console.log(data.progress); // 0.0 to 1.0
  }

  checkStatus();
  ```
</CodeGroup>

When the order is complete, the response includes download links:

```json theme={null}
{
  "status": "done",
  "progress": 1.0,
  "order_id": "2879fa0b-...",
  "results_files": {
    "xlsx": "https://api.keywordinsights.ai/api/keywords-insights/order/xlsx/2879fa0b-.../",
    "google_sheets": "https://docs.google.com/spreadsheets/d/.../copy"
  }
}
```

### Retrieving Results as JSON

Get paginated cluster data:

<CodeGroup>
  ```python 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}

  order_id = "2879fa0b-deae-4aab-ad87-fd8fb8db9717"

  response = requests.get(
      f"{BASE_URL}/api/keywords-insights/order/json/{order_id}/",
      headers=HEADERS,
      params={
          "page_size": 50,
          "page_number": 1,
          "sort_by": "search_volume",
          "ascending": False,
      },
  )

  data = response.json()
  clusters = data["result"]["payload"]["clusters"]

  for cluster in clusters:
      print(f"{cluster['name']} — {cluster['number_of_keywords']} keywords, vol: {cluster['search_volume']}")
  ```

  ```javascript JavaScript theme={null}
  const API_KEY = process.env.KWI_API_KEY;
  const BASE_URL = "https://api.keywordinsights.ai";
  const HEADERS = { "X-API-Key": API_KEY };

  const orderId = "2879fa0b-deae-4aab-ad87-fd8fb8db9717";

  async function getResults() {
    const url = new URL(`${BASE_URL}/api/keywords-insights/order/json/${orderId}/`);
    url.searchParams.append("page_size", "50");
    url.searchParams.append("page_number", "1");
    url.searchParams.append("sort_by", "search_volume");
    url.searchParams.append("ascending", "false");

    const response = await fetch(url, { headers: HEADERS });
    const data = await response.json();
    const clusters = data.result.payload.clusters;

    clusters.forEach((cluster) => {
      console.log(
        `${cluster.name} — ${cluster.number_of_keywords} keywords, vol: ${cluster.search_volume}`
      );
    });
  }

  getResults();
  ```
</CodeGroup>

**JSON results query parameters:**

| Parameter     | Type    | Default         | Description                                                |
| ------------- | ------- | --------------- | ---------------------------------------------------------- |
| `page_size`   | integer | 50              | Results per page (max 1,000)                               |
| `page_number` | integer | 1               | Page number                                                |
| `sort_by`     | string  | `search_volume` | Sort field (e.g. `search_volume`, `keyword`, `cluster_id`) |
| `ascending`   | boolean | `false`         | Sort direction                                             |
| `filter_id`   | string  | —               | Filter ID from the filters endpoint                        |

### Exporting as XLSX

Download results as an Excel file:

<CodeGroup>
  ```python 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}

  order_id = "2879fa0b-deae-4aab-ad87-fd8fb8db9717"

  response = requests.get(
      f"{BASE_URL}/api/keywords-insights/order/xlsx/{order_id}/",
      headers=HEADERS,
  )

  # Save the XLSX file
  with open("clusters.xlsx", "wb") as f:
      f.write(response.content)
  ```

  ```javascript JavaScript theme={null}
  const fs = require("fs");
  const API_KEY = process.env.KWI_API_KEY;
  const BASE_URL = "https://api.keywordinsights.ai";
  const HEADERS = { "X-API-Key": API_KEY };

  const orderId = "2879fa0b-deae-4aab-ad87-fd8fb8db9717";

  async function downloadXlsx() {
    const response = await fetch(`${BASE_URL}/api/keywords-insights/order/xlsx/${orderId}/`, {
      headers: HEADERS,
    });

    const buffer = await response.arrayBuffer();
    fs.writeFileSync("clusters.xlsx", Buffer.from(buffer));
  }

  downloadXlsx();
  ```
</CodeGroup>

### Estimating Cost

Check how many credits an order will cost before creating it:

<CodeGroup>
  ```python Python theme={null}
  response = requests.get(
      f"{BASE_URL}/api/keywords-insights/order/cost/",
      headers=HEADERS,
      params={"n_keywords": 500, "insights": ["cluster", "context"]},
  )

  print(response.json())  # {"cost": 1000}
  ```

  ```javascript JavaScript theme={null}
  async function estimateCost() {
    const url = new URL(`${BASE_URL}/api/keywords-insights/order/cost/`);
    url.searchParams.append("n_keywords", "500");
    url.searchParams.append("insights", "cluster");
    url.searchParams.append("insights", "context");

    const response = await fetch(url, { headers: HEADERS });
    const data = await response.json();
    console.log(data); // { cost: 1000 }
  }

  estimateCost();
  ```
</CodeGroup>

**Cost per keyword by insight combination:**

| Insights                       | Cost per keyword |
| ------------------------------ | ---------------- |
| `cluster` only                 | 1 credit         |
| `cluster` + `context`          | 2 credits        |
| `cluster` + `rank`             | 2 credits        |
| `cluster` + `context` + `rank` | 3 credits        |

### Complete Example

End-to-end workflow: create an order, wait for completion, and download results.

<CodeGroup>
  ```python 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_order(keywords, volumes):
      """Create a clustering order."""
      response = requests.post(
          f"{BASE_URL}/api/keywords-insights/order/",
          headers=HEADERS,
          json={
              "project_name": "API automation example",
              "keywords": keywords,
              "search_volumes": volumes,
              "language": "en",
              "location": "United States",
              "insights": ["cluster", "context"],
              "folder_id": "<your_folder_id>",
              "clustering_method": "volume",
              "grouping_accuracy": 4,
              "hub_creation_method": "medium",
          },
      )
      response.raise_for_status()
      return response.json()


  def wait_for_completion(order_id):
      """Poll until the order is done."""
      while True:
          response = requests.get(
              f"{BASE_URL}/api/keywords-insights/order/",
              headers=HEADERS,
              params={"order_id": order_id},
          )
          data = response.json()
          print(f"Status: {data['status']} ({data['progress']:.0%})")

          if data["status"] == "done":
              return data

          time.sleep(30)


  def get_results(order_id):
      """Fetch all clusters as JSON."""
      response = requests.get(
          f"{BASE_URL}/api/keywords-insights/order/json/{order_id}/",
          headers=HEADERS,
          params={"page_size": 100, "page_number": 1},
      )
      response.raise_for_status()
      return response.json()


  # --- Run ---

  keywords = ["best running shoes", "running shoes review", "top sneakers for running"]
  volumes = [12000, 8500, 3200]

  result = create_order(keywords, volumes)
  order_id = result["order_id"]
  print(f"Order created: {order_id} (cost: {result['cost']} credits)")

  wait_for_completion(order_id)

  data = get_results(order_id)
  for cluster in data["result"]["payload"]["clusters"]:
      print(f"  {cluster['name']} — {cluster['number_of_keywords']} kw, vol: {cluster['search_volume']}")
  ```

  ```javascript JavaScript theme={null}
  const API_KEY = process.env.KWI_API_KEY;
  const BASE_URL = "https://api.keywordinsights.ai";
  const HEADERS = { "X-API-Key": API_KEY };

  async function createOrder(keywords, volumes) {
    const response = await fetch(`${BASE_URL}/api/keywords-insights/order/`, {
      method: "POST",
      headers: { ...HEADERS, "Content-Type": "application/json" },
      body: JSON.stringify({
        project_name: "API automation example",
        keywords,
        search_volumes: volumes,
        language: "en",
        location: "United States",
        insights: ["cluster", "context"],
        folder_id: "<your_folder_id>",
        clustering_method: "volume",
        grouping_accuracy: 4,
        hub_creation_method: "medium",
      }),
    });

    if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
    return await response.json();
  }

  async function waitForCompletion(orderId) {
    while (true) {
      const url = new URL(`${BASE_URL}/api/keywords-insights/order/`);
      url.searchParams.append("order_id", orderId);

      const response = await fetch(url, { headers: HEADERS });
      const data = await response.json();

      console.log(`Status: ${data.status} (${(data.progress * 100).toFixed(0)}%)`);

      if (data.status === "done") return data;

      await new Promise((r) => setTimeout(r, 30000));
    }
  }

  async function getResults(orderId) {
    const url = new URL(`${BASE_URL}/api/keywords-insights/order/json/${orderId}/`);
    url.searchParams.append("page_size", "100");
    url.searchParams.append("page_number", "1");

    const response = await fetch(url, { headers: HEADERS });
    if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
    return await response.json();
  }

  async function run() {
    const keywords = ["best running shoes", "running shoes review", "top sneakers for running"];
    const volumes = [12000, 8500, 3200];

    const result = await createOrder(keywords, volumes);
    const orderId = result.order_id;
    console.log(`Order created: ${orderId} (cost: ${result.cost} credits)`);

    await waitForCompletion(orderId);

    const data = await getResults(orderId);
    data.result.payload.clusters.forEach((cluster) => {
      console.log(
        `  ${cluster.name} — ${cluster.number_of_keywords} kw, vol: ${cluster.search_volume}`
      );
    });
  }

  run();
  ```
</CodeGroup>
