# Bulk AI Dataset Enricher – Run LLM Prompts on Every Row (`gazidev/bulk-ai-dataset-enricher`) Actor

Run an AI prompt on every row of an Apify dataset, CSV, Excel or JSON list: classify, tag sentiment, extract fields, summarize or translate with GPT, Gemini, Claude, Llama and 400+ models. No API key. $0.80 per 1,000 rows + LLM tokens.

- **URL**: https://apify.com/gazidev/bulk-ai-dataset-enricher.md
- **Developed by:** [Cemal Atakli](https://apify.com/gazidev) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.80 / 1,000 row enricheds

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

## Bulk AI Dataset Enricher – Run LLM Prompts on Every Row (GPT, Gemini, Claude)

Run an **AI prompt on every row** of an Apify dataset, a CSV / Excel / Google Sheets file or a pasted JSON list, and get your rows back with **new AI columns**: sentiment, category, extracted fields, summaries, translations, lead scores, reply drafts, anything you can describe in a prompt.

Write a **prompt template with `{{column}}` placeholders**, pick a model (Gemini, GPT, Claude, Llama, Mistral, DeepSeek or any of 400+ OpenRouter models) and choose one text column or **structured JSON fields** (each field becomes its own column). Rows are processed in parallel with automatic retries.

**No API key needed.** LLM calls go through [Apify's own OpenRouter Actor](https://apify.com/apify/openrouter) with your run's Apify token.

**$0.80 per 1,000 enriched rows + LLM token cost.** Failed rows are free.

### How you pay (two parts, both on your Apify bill)

1. **This Actor's fee: $0.0008 per row enriched** (plus a $0.00005 start fee). Rows that fail, return invalid JSON or are skipped are not charged.
2. **LLM token cost** for each call, billed by Apify to **your Apify account** through Apify's OpenRouter Actor (`apify/openrouter`) at OpenRouter's prices. With the default model (Gemini 2.5 Flash Lite) a typical row of ~250 input and ~60 output tokens costs about **$0.00005**. Note: Apify bills OpenRouter usage on the **free plan at 10x** the OpenRouter price.

Set a maximum cost per run in the run options and the Actor stops cleanly before going over it.

### What people use it for

- **Classify scraped data**: tag leads, products, jobs or companies into your own categories ("SaaS / agency / e-commerce").
- **Review and comment analysis**: sentiment, main topic, "needs a reply?" and a reply draft for each review.
- **Extract structured fields** from messy text: company, role, email found, price, size, address parts, dates.
- **Lead qualification**: score each lead 1-10 with a reason, from your scraper's output.
- **Summarize** long descriptions, articles, posts or tickets into one line.
- **Translate** product titles, reviews or listings into any language.
- **Clean and normalize**: fix capitalization, standardize job titles, map free-text countries to ISO codes.

It pairs naturally with any Apify scraper: run the scraper, then pass its dataset (or use `{{resource.defaultDatasetId}}` in an integration) to enrich every item automatically.

### Input

| Field | Meaning |
|---|---|
| `datasetIds` | Apify datasets to enrich (resource picker) |
| `fileUrls` | CSV, TSV, XLSX, XLS, ODS, JSON or JSON Lines URLs. Google Sheets ("Anyone with the link"), Drive, Dropbox, OneDrive and GitHub links work |
| `items` | Or paste a JSON array of rows |
| `task` | `custom` (your template), or presets `sentiment`, `classify`, `extract`, `summarize`, `translate` |
| `prompt` | Template with `{{column}}`, dot paths like `{{address.city}}`, or `{{row}}` for the whole row as JSON |
| `outputMode` | `text` = one column (`outputColumn`), `json` = structured fields from `outputFields` |
| `outputFields` | `{"field": "description"}`. `a\|b\|c` = one of these labels, `number ...`, `integer ...`, `boolean ...`, `array ...`, anything else = text |
| `model` / `customModel` | Model from the list, or any OpenRouter model ID |
| `maxRows`, `concurrency`, `maxOutputTokens`, `maxInputChars`, `temperature` | Limits and performance |
| `keepOriginalColumns`, `keepColumns`, `includeUsage` | Output shape |

#### Example: review analysis with structured fields

```json
{
  "datasetIds": ["YOUR_REVIEWS_DATASET_ID"],
  "prompt": "Analyze this customer review.\nProduct: {{product}}\nStars: {{rating}}\nReview: {{review}}",
  "outputMode": "json",
  "outputFields": {
    "sentiment": "positive|negative|neutral|mixed",
    "mainTopic": "short topic, e.g. battery, shipping, noise, quality",
    "needsReply": "boolean - true if customer support should respond",
    "replyDraft": "one polite sentence the shop could reply with, in the review's language"
  },
  "maxRows": 1000
}
```

#### Example: classify leads from a CSV

```json
{
  "fileUrls": ["https://docs.google.com/spreadsheets/d/YOUR_SHEET_ID/edit"],
  "task": "classify",
  "labels": ["SaaS", "Agency", "E-commerce", "Other"],
  "prompt": "{{company_name}} - {{website}} - {{description}}"
}
```

#### Example: translate product titles

```json
{ "datasetIds": ["YOUR_DATASET_ID"], "task": "translate", "targetLanguage": "German", "prompt": "{{title}}" }
```

### Output

Each output row = **your original row + the AI columns + `aiError`** (null when everything worked). Rows keep their original order.

```json
{
  "product": "Wireless earbuds",
  "review": "Left earbud stopped working after two weeks. Support never answered my emails.",
  "rating": 1,
  "sentiment": "negative",
  "mainTopic": "defect / support",
  "needsReply": true,
  "replyDraft": "We're very sorry about your earbud and our slow reply; please reply with your order number and we'll send a replacement right away.",
  "aiError": null
}
```

Export as CSV, Excel, JSON or send it to Google Sheets with an integration. The run summary (`OUTPUT` record) shows rows enriched, errors, and total prompt / completion tokens.

### Pricing compared

| | This Actor | Typical bulk LLM runner |
|---|---|---|
| Fee per row | **$0.0008** | $0.002 |
| LLM tokens | your Apify account, OpenRouter price | similar |
| API key | not needed | often not needed |
| Inputs | dataset, CSV/XLSX/Google Sheets URL, JSON | usually dataset or JSON |
| Structured JSON columns | yes, with types and label lists | varies |
| Failed rows | free | varies |

### Use with AI agents (Apify MCP)

Add the Actor to Claude, ChatGPT, Cursor or any MCP client through the [Apify MCP server](https://mcp.apify.com). An agent can pass a dataset ID or JSON rows plus a prompt and get typed columns back, for example: *"Classify every company in this dataset as SaaS, agency or e-commerce and give a one-line reason."*

### FAQ

**Do I need an OpenAI / OpenRouter key?** No. The Actor uses your run's Apify token to call Apify's OpenRouter Actor; token costs appear on your Apify bill.

**Which model should I use?** Start with the default `google/gemini-2.5-flash-lite` (fast and cheap). For harder reasoning or nuanced writing try Claude Haiku 5.5, GPT-5.4 mini or Claude Sonnet 5.5.

**What happens when a row fails?** It is saved with an `aiError` message and is not charged. Rate limits and server errors are retried automatically (up to 4 attempts). If the model returns invalid JSON it is asked once more. Note that LLM tokens already used for a failed row are still billed by OpenRouter.

**What if a column is missing in some rows?** The placeholder becomes empty text. If all placeholders of a row are empty, the row is skipped for free.

**How many rows can I process?** Any number; datasets and files are streamed. Use `maxRows` for a test first and raise `concurrency` for speed.

**Is there an output limit?** Apify's OpenRouter Actor allows up to 2,048 output tokens per call, which is plenty for row-level tasks.

**Is my data used for training?** Rows are sent to the model provider you choose through OpenRouter; each provider's data policy applies (see openrouter.ai). Don't send data you are not allowed to process with third-party AI services.

### Related Actors

- [Dataset Filter & Transform](https://apify.com/gazidev/dataset-filter-transform)

# Actor input Schema

## `datasetIds` (type: `array`):

Apify datasets to enrich, e.g. the output of a scraper run. In an integration use {{resource.defaultDatasetId}}. All sources (datasets, files, pasted rows) are combined. Remove the example pasted rows when you use your own data.

## `fileUrls` (type: `array`):

Public URLs of CSV, TSV, XLSX, XLS, ODS, JSON or JSON Lines files. Google Sheets ('Anyone with the link'), Google Drive, Dropbox, OneDrive and GitHub links are converted to direct downloads.

## `items` (type: `array`):

A JSON array of objects (one object = one row). Handy for quick tests and AI agents.

## `maxRows` (type: `integer`):

Stop after this many rows in total (0 = all rows). You pay per enriched row, so start small.

## `task` (type: `string`):

Custom = your prompt template decides everything. The presets add a tested instruction and fixed output columns; the prompt template is then just the text to process (empty = the whole row).

## `prompt` (type: `string`):

Use {{column}} placeholders, e.g. {{title}}, {{address.city}} (dot paths), {{row}} (the whole row as JSON). Missing columns become empty text. Rows whose placeholders are all empty are skipped for free.

## `systemPrompt` (type: `string`):

Optional. Role, tone, language or rules, e.g. 'You are a B2B lead qualification expert. Answer in English.'

## `outputMode` (type: `string`):

Text = the answer goes into one column. JSON = the model must return the fields below, each saved as its own column.

## `outputColumn` (type: `string`):

Column for the answer in text mode.

## `outputFields` (type: `object`):

Field name -> description. Type is inferred: 'a|b|c' = one of these labels, 'number ...', 'integer ...', 'boolean ...', 'array ...' (list of strings), anything else = text. Full JSON-schema objects are accepted too.

## `labels` (type: `array`):

For the Classify task: the allowed categories, e.g. Sales lead, Support request, Spam.

## `targetLanguage` (type: `string`):

For the Translate task.

## `summaryMaxWords` (type: `integer`):

For the Summarize task.

## `model` (type: `string`):

LLM used through Apify's OpenRouter Actor. Prices are OpenRouter's per million input / output tokens, billed to your Apify account (Apify free plan: 10x). Most row tasks cost $0.00002-0.0002 per row with the default model.

## `customModel` (type: `string`):

Optional: any model ID from openrouter.ai/models (e.g. qwen/qwen3-235b-a22b-2507). Overrides the select above.

## `temperature` (type: `string`):

0 = consistent answers (recommended for classification/extraction), up to 1 = more creative.

## `maxOutputTokens` (type: `integer`):

Upper limit of the answer length (Apify's OpenRouter Actor caps it at 2,048).

## `maxInputChars` (type: `integer`):

Longer rendered prompts are truncated, which keeps token costs predictable.

## `concurrency` (type: `integer`):

How many rows are sent to the model at the same time. Rate limits are retried automatically.

## `keepOriginalColumns` (type: `boolean`):

On = original row + AI columns. Off = only the columns listed below + AI columns.

## `keepColumns` (type: `array`):

E.g. id, url, name.

## `includeUsage` (type: `boolean`):

Adds aiModel and aiTokens to every row.

## Actor input object example

```json
{
  "items": [
    {
      "product": "Wireless earbuds",
      "review": "Battery lasts all day and pairing was instant. Best purchase this year!",
      "rating": 5
    },
    {
      "product": "Wireless earbuds",
      "review": "Left earbud stopped working after two weeks. Support never answered my emails.",
      "rating": 1
    },
    {
      "product": "Coffee grinder",
      "review": "Grinds evenly but it is louder than I expected. Fine for the price.",
      "rating": 3
    },
    {
      "product": "Coffee grinder",
      "review": "Package arrived three days late and the box was crushed, but the grinder itself works.",
      "rating": 3
    },
    {
      "product": "Desk lamp",
      "review": "Lámpara preciosa, luz cálida y muy buena calidad. La recomiendo.",
      "rating": 5
    }
  ],
  "maxRows": 5,
  "task": "custom",
  "prompt": "Analyze this customer review.\nProduct: {{product}}\nStars: {{rating}}\nReview: {{review}}",
  "outputMode": "json",
  "outputColumn": "aiOutput",
  "outputFields": {
    "sentiment": "positive|negative|neutral|mixed",
    "mainTopic": "short topic, e.g. battery, shipping, noise, quality",
    "needsReply": "boolean - true if customer support should respond",
    "replyDraft": "one polite sentence the shop could reply with, in the review's language"
  },
  "targetLanguage": "English",
  "summaryMaxWords": 40,
  "model": "google/gemini-2.5-flash-lite",
  "temperature": "0",
  "maxOutputTokens": 512,
  "maxInputChars": 12000,
  "concurrency": 10,
  "keepOriginalColumns": true,
  "includeUsage": false
}
```

# Actor output Schema

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

No description

## `csv` (type: `string`):

No description

## `summary` (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 = {
    "items": [
        {
            "product": "Wireless earbuds",
            "review": "Battery lasts all day and pairing was instant. Best purchase this year!",
            "rating": 5
        },
        {
            "product": "Wireless earbuds",
            "review": "Left earbud stopped working after two weeks. Support never answered my emails.",
            "rating": 1
        },
        {
            "product": "Coffee grinder",
            "review": "Grinds evenly but it is louder than I expected. Fine for the price.",
            "rating": 3
        },
        {
            "product": "Coffee grinder",
            "review": "Package arrived three days late and the box was crushed, but the grinder itself works.",
            "rating": 3
        },
        {
            "product": "Desk lamp",
            "review": "Lámpara preciosa, luz cálida y muy buena calidad. La recomiendo.",
            "rating": 5
        }
    ],
    "maxRows": 5,
    "prompt": `Analyze this customer review.
Product: {{product}}
Stars: {{rating}}
Review: {{review}}`,
    "outputMode": "json",
    "outputFields": {
        "sentiment": "positive|negative|neutral|mixed",
        "mainTopic": "short topic, e.g. battery, shipping, noise, quality",
        "needsReply": "boolean - true if customer support should respond",
        "replyDraft": "one polite sentence the shop could reply with, in the review's language"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("gazidev/bulk-ai-dataset-enricher").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 = {
    "items": [
        {
            "product": "Wireless earbuds",
            "review": "Battery lasts all day and pairing was instant. Best purchase this year!",
            "rating": 5,
        },
        {
            "product": "Wireless earbuds",
            "review": "Left earbud stopped working after two weeks. Support never answered my emails.",
            "rating": 1,
        },
        {
            "product": "Coffee grinder",
            "review": "Grinds evenly but it is louder than I expected. Fine for the price.",
            "rating": 3,
        },
        {
            "product": "Coffee grinder",
            "review": "Package arrived three days late and the box was crushed, but the grinder itself works.",
            "rating": 3,
        },
        {
            "product": "Desk lamp",
            "review": "Lámpara preciosa, luz cálida y muy buena calidad. La recomiendo.",
            "rating": 5,
        },
    ],
    "maxRows": 5,
    "prompt": """Analyze this customer review.
Product: {{product}}
Stars: {{rating}}
Review: {{review}}""",
    "outputMode": "json",
    "outputFields": {
        "sentiment": "positive|negative|neutral|mixed",
        "mainTopic": "short topic, e.g. battery, shipping, noise, quality",
        "needsReply": "boolean - true if customer support should respond",
        "replyDraft": "one polite sentence the shop could reply with, in the review's language",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("gazidev/bulk-ai-dataset-enricher").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 '{
  "items": [
    {
      "product": "Wireless earbuds",
      "review": "Battery lasts all day and pairing was instant. Best purchase this year!",
      "rating": 5
    },
    {
      "product": "Wireless earbuds",
      "review": "Left earbud stopped working after two weeks. Support never answered my emails.",
      "rating": 1
    },
    {
      "product": "Coffee grinder",
      "review": "Grinds evenly but it is louder than I expected. Fine for the price.",
      "rating": 3
    },
    {
      "product": "Coffee grinder",
      "review": "Package arrived three days late and the box was crushed, but the grinder itself works.",
      "rating": 3
    },
    {
      "product": "Desk lamp",
      "review": "Lámpara preciosa, luz cálida y muy buena calidad. La recomiendo.",
      "rating": 5
    }
  ],
  "maxRows": 5,
  "prompt": "Analyze this customer review.\\nProduct: {{product}}\\nStars: {{rating}}\\nReview: {{review}}",
  "outputMode": "json",
  "outputFields": {
    "sentiment": "positive|negative|neutral|mixed",
    "mainTopic": "short topic, e.g. battery, shipping, noise, quality",
    "needsReply": "boolean - true if customer support should respond",
    "replyDraft": "one polite sentence the shop could reply with, in the review'\''s language"
  }
}' |
apify call gazidev/bulk-ai-dataset-enricher --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,gazidev/bulk-ai-dataset-enricher"
        }
    }
}
```

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/XJlFCBeW2FQQS2nw4/builds/urDDk5M7RLGA2ugTz/openapi.json
