Dataset Enrich — structure any dataset with your own LLM key avatar

Dataset Enrich — structure any dataset with your own LLM key

Pricing

Pay per usage

Go to Apify Store
Dataset Enrich — structure any dataset with your own LLM key

Dataset Enrich — structure any dataset with your own LLM key

Structure and enrich any dataset with YOUR OWN LLM key (OpenAI, Anthropic or OpenRouter). Define a target JSON schema and an instruction; every item is transformed to match, with JSON repair, retries and per-item error isolation. You control model choice and token cost.

Pricing

Pay per usage

Rating

0.0

(0)

Developer

Daniel James

Daniel James

Maintained by Community

Actor stats

0

Bookmarked

1

Total users

0

Monthly active users

7 days ago

Last modified

Share

Point this at a messy dataset, describe the shape you want as a JSON Schema, and get back clean, typed, one-row-per-item data — structured by your model on your API key.

That last part is the whole idea. You bring an OpenAI, Anthropic or OpenRouter key; you pick the model; the tokens are billed straight to your own provider account at your own rate. This actor is the thin, careful machinery around the call — batching, concurrency, rate-limit backoff, JSON repair and a dataset that exports cleanly — and nothing else. You control the cost and you control the model.

What you get

You define the columns. This is a target schema:

{
"type": "object",
"properties": {
"colour": { "type": ["string", "null"] },
"material": { "type": ["string", "null"] },
"size": { "type": ["string", "null"] },
"priceGbp": { "type": ["number", "null"] },
"shipsFrom": { "type": ["string", "null"] }
},
"required": ["colour", "priceGbp"]
}

and this is what lands in the dataset, one row per input item:

{
"colour": "blue",
"material": "cotton",
"size": "L",
"priceGbp": 22.5,
"shipsFrom": "Leeds",
"_index": 0,
"_sourceId": "sku-1"
}

Your fields sit at the top level — so the dataset exports to CSV or Excel as the flat table you designed, with no unwrapping. The only additions are _index (position in the input) and _sourceId (the item's own id/url/sku where it had one), which are there so you can join the enriched rows back to the source. Add _source too with includeSourceItem if you would rather carry the original row along.

What you can do with it

  • Normalise a scrape. Free-text product blurbs, job descriptions, listings or reviews into typed columns.
  • Classify at scale. Sentiment, category, intent, priority, lead quality — one enum field in your schema.
  • Extract entities. Company, location, salary band, contact type, part number — from prose that has no fields at all.
  • Translate or rewrite into a fixed shape, then export.
  • Chain it. Run any other actor, then feed its dataset ID straight in here and get a structured version of it.

Bring your own key

ProviderEndpoint calledJSON enforcement used
OpenAIapi.openai.com/v1/chat/completionsresponse_format: json_object
Anthropicapi.anthropic.com/v1/messagesforced tool call whose input schema is your schema
OpenRouteropenrouter.ai/api/v1/chat/completionsresponse_format: json_object

Your key is handled as a secret and nothing else. It is marked isSecret in the input schema, so Apify encrypts it at rest; at run time it is held in memory and put into the Authorization / x-api-key header of the call to your provider — and that is the only place it ever goes. This actor never logs it, never puts it in the run's status message, and never writes it to the dataset or to any storage of its own. Provider error bodies sometimes echo a rejected key straight back at you; every string this actor emits is scrubbed of the key before it leaves, so even that cannot leak it.

Model ids: give the model exactly as your provider spells it — for example gpt-4o-mini (OpenAI), claude-3-5-haiku-latest (Anthropic), openai/gpt-4o-mini (OpenRouter). Those are illustrations, not a supported list: providers add and retire models constantly, so check your own provider's model list. A small, cheap model is normally more than enough for structuring, and it is your bill.

When the model misbehaves — because it will

A structuring run that dies on item 400 of 5,000 is worthless. This one does not die:

  1. Loose parsing first. Fences, prose before or after the JSON, a single object where an array was asked for — all recovered locally, for free, without another call.
  2. One repair retry. If it is still not JSON, the model is shown its own reply back and asked for valid JSON only. Exactly one retry: a model that cannot manage it twice will not manage it on the third attempt, and every attempt is your money.
  3. Then an honest row. Still broken? That item gets {"_error": "...", "_raw": "..."} instead — the reason, and the reply that could not be parsed — and the run carries on. Check the Errors view in the dataset.

The same applies to the API itself: 429s and 5xxs are retried with exponential backoff and the provider's own Retry-After is honoured; a 401, 404 or 400 is not retried at all, because a bad key or a bad model id will fail identically forever. If your model or your schema turns out not to support the provider's structured-output mode, the run notices once, drops to prompt-only JSON, and continues rather than failing every batch.

Input

FieldExampleNotes
datasetIdKhNDDNIQRowKYqlD7the dataset to read — typically another actor's run output
items[{"id":"1","text":"…"}]paste rows in directly instead; used only when datasetId is empty
instruction"Extract the product attributes…"what to do with each row, in plain English. Say what to do when a value is missing
outputSchemasee aboveJSON Schema for one output row. Its properties become your columns
provideropenaiopenai, anthropic or openrouter
apiKeysk-…your key. Stored encrypted, never logged, never written to the dataset
modelgpt-4o-miniexactly as your provider spells it
batchSize1rows per model call. Bigger = fewer tokens overall, but a longer reply to get wrong. 1–5 is the safe range
concurrency2parallel calls, capped at 5
maxItems00 = everything. Set it to 10 for a trial run first
includeSourceItemfalsekeep the untouched source row under _source

Getting a good result

  • Be specific in the instruction, and say what "unknown" looks like — "use null for anything the text does not state; never guess" is worth more than any schema tweak.
  • Type your schema properly. {"type": ["number", "null"]} gets you a number column; {"type": "string"} gets you "22.50" as text.
  • Use an enum for classification fields. It is the single biggest accuracy win available.
  • Keep the schema flat if you plan to export to CSV.
  • Start with maxItems: 10. Look at the rows, fix the instruction, then run the lot.

Honest limits

  • Quality is your model's, not ours. This actor guarantees the plumbing — one row in, one row out, valid JSON or an explicit error. It cannot make a weak model accurate, and it does not validate the model's output against your schema field by field: the schema steers the model and defines your columns, it is not a post-hoc assertion.
  • You pay your provider directly for every token, including the tokens spent on a repair retry and on rows that ultimately fail. maxItems is your seatbelt.
  • Large batches truncate. If a reply hits the model's output limit it arrives as broken JSON and becomes error rows. Lower batchSize if you see that.
  • Three providers, on their standard endpoints. Azure OpenAI, Bedrock, Vertex and self-hosted gateways are not supported (OpenRouter covers a very wide model list if yours is not on OpenAI or Anthropic directly).
  • Row order is not guaranteed in the dataset, because batches run concurrently. _index always is — sort on it.
  • Send only data you are entitled to send. Rows go to the provider you choose, under their terms and their retention policy, not ours. If your dataset contains personal data, that is your lawful basis and your processor agreement to hold.

Typical costs

Two separate bills, and it is worth being clear about which is which:

  • Your provider charges you for tokens, at your rate, on your account. This is the dominant cost and it is entirely under your control — model, batchSize and maxItems are the three dials.
  • This actor charges per enriched row. Platform compute is negligible: it is an I/O-bound HTTP loop with no browser and no proxies, and it spends nearly all of its wall time waiting on your provider. Rows that fail are not charged — you are billed for structured data, not for attempts.