AI for Every Row: Bulk LLM to Classify & Summarize Datasets
Pricing
from $1.40 / 1,000 row processeds
AI for Every Row: Bulk LLM to Classify & Summarize Datasets
Run an LLM (GPT, Gemini and more) on every row of a dataset or JSON list, for AI enrichment and sentiment analysis: classify, tag, score, summarize or extract fields, chained after any scraper with datasetId. Answers validated against your schema or labels; $2 per 1,000 valid rows plus model tokens.
Pricing
from $1.40 / 1,000 row processeds
Rating
0.0
(0)
Developer
Michael Costa
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
4 hours ago
Last modified
Categories
Share
What does AI for Every Row do?
AI for Every Row runs an LLM on every row of a dataset: classify, tag, score, summarize or pull fields out of each one. Give it any Apify dataset (chain it after a review, lead, job or web scraper) or a JSON list, say which fields hold the text, and what you want back: labels to choose from, plain-English fields, or a JSON Schema. Every answer is validated, and you pay only for rows that match. Typical jobs: AI enrichment of leads or listings, and sentiment analysis of reviews.
It is not a scraper: it works on rows you already have. To read web pages, use AI Data Extractor instead (which one?).
Input fields · API · Use it from Claude, ChatGPT or Cursor (MCP)
Jump to: Fields · Price · How to use · Chain after a scraper · Only new rows · Input · Output · AI agents (MCP) · Limits · FAQ
Try it in one click: the input comes pre-filled with three short product reviews we made up, and the labels positive, negative and mixed. That's 3 rows: $0.006, plus the $0.00005 start fee, plus well under $0.001 of model tokens on a paid Apify plan. Then replace them with your own rows or dataset.
What data does AI for Every Row return?
One result per input row, in the order they finish, including the rows that failed (with the reason, never charged).
| Field | Example | Notes |
|---|---|---|
data | {"label": "negative"} | The answer: {label} with labels, your fields as camelCase keys, or your schema's shape. |
valid | true | true when data matches the schema. Only these rows are charged. |
errors | ["label: 'furious' is not one of ['positive', 'negative', 'mixed']"] | Why a row has no valid answer: no text, a failed call, or what didn't match. Empty when valid. |
sourceIndex | 1 | The row's position in your input (0-based). |
kept | {"id": "r2"} | The keepFields copied from the row, to join results back; null when none are set. |
model | openai/gpt-4.1-mini | The model that read the row. |
inputTokens, outputTokens | 283, 5 | Tokens the model reported: what the token charge is based on. |
textChars, truncated | 98, false | Characters of row text sent; true if it was cut to fit maxTokensPerRow. |
processedAt | 2026-10-02T14:03:11Z | When the run read the row. |
The full list is under Output.
How much does it cost to run an LLM on every row?
Two parts, both on your Apify account:
- This actor: $2.00 per 1,000 rows processed ($0.002 a row), plus $0.00005 each time a run starts. Only rows whose answer matched your schema or labels count.
- The model's tokens, billed by Apify's own OpenRouter actor, which this
actor calls with your run's token. On paid Apify plans that's OpenRouter's list price; on the Free plan the
OpenRouter actor charges 10 times as much (its pricing, not ours). A short review with a label took about 220
input and 6 output tokens (measured on 200 rows, 2026-10-02): with the default
openai/gpt-4.1-mini, about $0.10 per 1,000 rows on a paid plan. Longer texts cost more;maxTokensPerRowcaps it, andopenai/gpt-4.1-nanocosts about a quarter as much.
- The example below: 3 rows × $0.002 = $0.006, plus the start fee and under $0.001 of tokens.
- A month, for example: 20,000 new reviews a month from a scheduled review scraper, each labelled and summarized in one sentence with Only new rows on: 20,000 × $0.002 = $40, plus about $4-6 of tokens (paid plan).
- Caps: Max rows per run in the input, and Maximum cost per run in the run options (it covers this actor's charges; the token charges are the OpenRouter actor's, so cap those with Max rows per run and Max tokens of text per row). Each row is counted against the limit before it's sent, and given back if it fails, so a capped run never sends a row it can't return.
Never charged by this actor: rows with no text in your text fields, failed model calls, answers that don't
match your schema or labels, answers with nothing in them (every value null, blank or a stand-in like "N/A"), and
rows skipped by Only new rows. (A row that reached the model still used tokens.)
How to classify or summarize every row of a dataset
- Open AI for Every Row and click Try for free (or Start if you're signed in).
- Pick your rows: a dataset in Or: rows from a dataset, or paste a JSON array into Rows.
- In Text fields, name the fields that hold the text, e.g.
text,title,description(dotted paths such asreview.bodywork). Only these go to the model. - Say what you want back: Labels to choose from (
positive,negative,mixed), or fields to get in plain English (summary: one sentence, topics, language), or a JSON Schema. Add Instructions for the task ("Classify each company's industry from its description"). - Name the Fields to copy to the output you'll join on (
id,url), click Start, then open the Output tab and export as JSON, CSV or Excel.
Labels, plain-English fields or a JSON Schema?
- Labels are for classification: the model must pick exactly one, returned as
data.label; anything else comes backvalid: false, uncharged. They apply only when fields and schema are empty. - Fields are quickest for everything else: each becomes a camelCase key (
main complaint→mainComplaint) holding text, a number, true/false, a list of texts ornull. Add a hint after a colon:score: 1 to 5. - A JSON Schema gives exact types, enums and nesting, e.g. a label and a score:
{"type": "object", "properties": {"label": {"enum": ["bug", "feature", "question"]}, "urgency": {"type": "integer", "minimum": 1, "maximum": 5}}, "required": ["label", "urgency"]}.
Chain it after any scraper
Point it at another actor's results and each item is one row. Only the top-level fields named in Text fields and Fields to copy are read from the dataset, so big items (a place with all its reviews) cost little to read.
- Once: pick the dataset in Or: rows from a dataset (
datasetId) and click Start. - Every time the other actor finishes: on that actor (or its task), open Integrations, add Run an actor
or task, pick AI for Every Row, and set the input to include
"datasetId": "{{resource.defaultDatasetId}}"plus yourtextFieldsand labels, fields or schema.
Good pairs from our own actors (any other actor's dataset works the same way):
| First run | Then label or summarize | Text fields |
|---|---|---|
| Website & Page to Markdown | Each page's topic, audience or a summary | markdown |
| AI Data Extractor | Classify what it extracted (e.g. a company's services into your industry list) | data |
| Product Price & Stock Monitor | Each product's category in your own taxonomy | name, brand |
| Hiring Signals | Which teams each company is hiring for, from its new roles | company, newRoles |
Have a CSV or Excel file instead? Turn it into a dataset first with Dataset Transformer, then chain this after it.
Only new rows, for scheduled pipelines
Turn on Only new rows ("onlyNew": true) and each run skips rows whose text the same setup already answered
validly in an earlier run, so a daily run after a scraper only pays for what's new. A run with nothing new costs only
the start fee.
- What counts as the same setup: the labels, fields or schema, the instructions, the model and the text fields. Change any of them and every row is new to it. The dataset doesn't matter: the same text gets the same answer.
- What's remembered: a hash of each row's text (not the text), in a key-value store named
ai-rows-seenin your own account, for the last 100,000 rows per setup. Rows that failed or didn't validate aren't remembered, so the next run tries them again. Two identical texts in one run are sent once. - Big datasets: a run that nears its timeout stops starting rows, saves what it finished and ends cleanly; the next run with Only new rows carries on where it stopped.
Example: sentiment of three reviews
The pre-filled input (three reviews we wrote; product is never sent, only text). On Apify on 2026-10-02 the
default input labelled all three as intended (positive, negative, mixed) in 9 seconds:
{"rows": [{"id": "r1", "product": "Steel kettle", "text": "Boils fast and the handle stays cool. Exactly what I wanted for the price."},{"id": "r2", "product": "Steel kettle", "text": "Started leaking from the base after two weeks, and support never answered my emails."},{"id": "r3", "product": "Desk lamp", "text": "Lovely warm light and easy to assemble, but the switch feels flimsy."}],"textFields": ["text"],"labels": ["positive", "negative", "mixed"],"keepFields": ["id"]}
What the model is sent for the second row, all of it besides the instructions and the schema:
text: Started leaking from the base after two weeks, and support never answered my emails. One result:
{"sourceIndex": 1,"kept": {"id": "r2"},"data": {"label": "negative"},"valid": true,"errors": [],"model": "openai/gpt-4.1-mini","textChars": 90,"inputTokens": 218,"outputTokens": 6,"truncated": false,"processedAt": "2026-10-02T14:24:41Z"}
Input
| Field | What it does |
|---|---|
Rows (rows) | A JSON array of objects (a plain string is {"text": ...}). Ignored when a dataset is set. |
Or: rows from a dataset (datasetId) | Another actor's results; chain with {{resource.defaultDatasetId}}. |
Text fields (textFields) | The fields the model reads, default text. The only fields sent. |
Labels to choose from (labels) | Classification: one label per row, as data.label. Default positive, negative, mixed. |
Or: fields to get (fields) | Plain-English fields. Overrides labels. |
Or: JSON Schema for one record (schema) | A JSON Schema with "type": "object" at the top. Overrides fields and labels. |
Instructions (instructions) | The task in your words, up to 4,000 characters. |
Model (model) | Any OpenRouter model id with structured outputs. Default openai/gpt-4.1-mini. |
Fields to copy to the output (keepFields) | Copied into kept for joins; not sent to the model. |
Only new rows (onlyNew) | Skip rows this setup already answered. Default off. |
Max rows per run (maxRows) | Send at most this many rows (1-20,000); empty = all. |
Max tokens of text per row (maxTokensPerRow) | Cap on each row's text, default 4,000 (100-100,000). |
{"datasetId": "<your dataset id>","textFields": ["description"],"instructions": "Classify each company by its main industry, from its description.","labels": ["Software", "Finance", "Healthcare", "Retail", "Manufacturing", "Other"],"keepFields": ["name", "website"],"model": "openai/gpt-4.1-mini","maxRows": 500}
Which model? The default, openai/gpt-4.1-mini, is accurate and cheap for labels and short fields. For big
batches of short rows, openai/gpt-4.1-nano or google/gemini-2.5-flash-lite cost about a quarter as much; for
subtle judgments or long texts, a larger model. The model must support structured outputs.
Output
One result per row:
{"sourceIndex": 4,"kept": {"id": "r5"},"data": {"label": "furious"},"valid": false,"errors": ["label: 'furious' is not one of ['positive', 'negative', 'mixed']"],"model": "openai/gpt-4.1-mini","textChars": 41,"inputTokens": 214,"outputTokens": 6,"truncated": false,"processedAt": "2026-10-02T14:03:11Z"}
A row with no text in your text fields has data: null, textChars: 0 and the reason in errors, and isn't sent.
The run's RUN_STATS record counts rows read, processed, invalid, empty, failed, without text, and skipped by
Only new rows (done before, or a repeat in the run), tokens used, and the options that applied. The Tokens view of the dataset shows
tokens per row.
Run it on a schedule, or from your own code
- Save your input as a task (Save as a new task, top right of the actor page) and add it to a schedule (Console → Schedules → Create new), or chain it after a scheduled scraper (how).
- Collect results: download the dataset as JSON, CSV or Excel; fetch the latest run's results from the
API
(
GET https://api.apify.com/v2/actor-tasks/<task id>/runs/last/dataset/items?status=SUCCEEDED&format=csv, with your API token); let a webhook tell your system when a run succeeds; or connect it to Make, Zapier or n8n through Apify's integrations.
Can I use AI for Every Row from an AI agent (MCP)?
Yes, through Apify's MCP server, from Claude, ChatGPT, Cursor or any other MCP client. Add this to your client's MCP configuration (or let the agent find it with the server's actor search); your client signs you in to Apify:
{"mcpServers": {"apify": {"url": "https://mcp.apify.com?tools=humble-echidna/ai-rows"}}}
To use an Apify API token instead of signing in, add
"headers": {"Authorization": "Bearer <APIFY_TOKEN>"} next to url.
An agent can label a few rows itself; this pays off for a whole dataset: pass
{"datasetId": "...", "textFields": ["text"], "labels": ["bug", "feature", "question"], "maxRows": 1000} and get one
validated answer per row without spending the agent's own context on the rows.
Who it's for
Anyone with a scheduled scraper (reviews, leads, jobs, listings, articles) who wants each row labelled, scored or summarized on its way to a spreadsheet or CRM: review sentiment and complaint themes, lead industry and size, job seniority, ticket triage, article summaries.
Why this one?
- Takes any Apify dataset. Chain it after any actor with
datasetId, no export and re-upload; or pass rows inline. It reads only the fields it needs. - Validated, not just generated. Every answer is checked against your labels or schema here, whatever the model
promised. One that doesn't match is marked
valid: falsewith the reasons, and isn't charged. - Only new rows. A scheduled run pays only for rows it hasn't answered before.
- Only the text you pick goes to the model. Names, emails and every other field stay out of the request.
- $2 per 1,000 rows, tokens at the model's price on paid plans. Pick any OpenRouter model;
inputTokensandoutputTokensshow the token cost per row.
Limits
- One call per row, one answer per row. For an answer about many rows together (a summary of all reviews), this isn't the tool.
- Up to 20,000 rows per run. For bigger datasets, schedule it with Only new rows; each run takes the next rows.
- Speed: 20 rows in flight at once; the model's answer time sets the pace. The same 200 short reviews took
22 seconds in one run and 106 in another on Apify (2026-10-02,
openai/gpt-4.1-nano), so plan on 2-10 rows a second. A run that nears its timeout (1 hour by default) stops cleanly and says so; with Only new rows on, the next run continues. - Your text fields are checked first. If none of the first 100 rows has text in them, the run stops before sending anything and lists the fields the first row has.
- Long texts are cut to
maxTokensPerRow(the start is kept;truncated: true). Raise it for long texts. - Answers are capped at 2,048 tokens by Apify's OpenRouter proxy. A longer answer comes back
valid: falsewith the reason, uncharged: ask for fewer fields or shorter values. - Models can still be wrong, even with a valid answer. Validation checks the shape, not the judgment; spot-check a sample before you rely on a new setup.
- No files. It reads datasets and inline rows; for a CSV, Excel or JSON file, load it with Dataset Transformer first.
FAQ
AI for Every Row or AI Data Extractor?
Extract from web pages → AI Data Extractor: give it page URLs (or a dataset of URLs) and it fetches each page and extracts fields. Already have rows → AI for Every Row: it never fetches anything, and works on the text already in your rows (reviews, descriptions, Markdown from Website & Page to Markdown).
What is sent to the model?
Only the values of your text fields, each as field: value, plus your instructions and the schema. Every other
field of the row, including names, emails and ids, stays in your account. Fields you copy with keepFields go to
the output, not to the model.
Can I use it on reviews or other text about people?
Labelling each review or message (sentiment, topic, urgency) is what it's for. Don't use it to profile people: to
score, rank or infer things about the individuals who wrote the text (their character, health, politics or the
like). Leave author names and ids out of the text fields; keep them with keepFields only if you need to join, and
only if you have a lawful basis to hold them.
Who pays for the model?
You do, through your own Apify account: the actor calls Apify's OpenRouter actor with your run's token, and Apify bills the tokens to the account running the actor. No API key is needed and none of ours is used.
Why did a row come back valid: false?
The errors field says why: the row had no text in your text fields, the model call failed, the answer wasn't a
label from your list, or it didn't match your schema. None of these are charged. Run it again with Only new
rows on and only those rows are retried.
Related actors
| Actor | Use it when |
|---|---|
| AI Data Extractor | You have web page URLs, not rows: it fetches each page and extracts fields. |
| Website & Page to Markdown | You want page text to label or summarize here. |
| Dataset Transformer | You need to load a CSV/Excel file, or filter and reshape the results afterwards. |
Feedback and support
Found a bug, or a task it handles badly? Open an issue on the Issues tab with the input you used.
Versions
Current version: 0.1. See the Changelog tab for what changed in each version.
