# Dataset Transform: Rename, Flatten, Filter & Sort Columns (`data-done-right/dataset-transform`) Actor

Dataset transform tool: rename columns, flatten JSON, filter rows, sort, sample, split, add computed columns and cast types on Apify datasets, CSV, JSON, JSONL and Excel files. Ordered JSON pipeline, no code. Export CSV or Excel.

- **URL**: https://apify.com/data-done-right/dataset-transform.md
- **Developed by:** [Data Done Right](https://apify.com/data-done-right) (community)
- **Categories:** Developer tools, Automation, Integrations
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.36 / 1,000 row transformeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## Dataset Transform: Rename, Flatten, Filter & Sort Columns

Dataset transform tool: run an ordered pipeline of steps on an Apify dataset, a CSV, JSON, JSONL or Excel file, or rows you paste. Rename columns, flatten JSON, filter rows, sort, sample, split, explode lists and add computed columns. The pipeline is plain JSON, so no code runs and nothing needs a script.

**What you get**

- Transformed rows in the run's dataset, plus an optional CSV, Excel, JSON or JSONL file with a download link.
- A `SUMMARY` record: rows in and out, rows in and out of every step, value problems with examples, source errors.
- An early, plain-language error for a bad pipeline, before any row is charged.

**What it does not do:** it does not merge or join several sources by a key (it only reads them one after another; see Related tools), does not run your own code or scripts, does not fetch web pages, and does not do fuzzy matching. Sorting and random sampling hold the rows in memory.

### How to transform a dataset

1. Put your rows in one of the sources: pick Apify datasets, give file links, or paste items.
2. Describe the steps in `operations`, top to bottom.
3. Run. Optionally ask for `csv` or `xlsx` in `exports`.

#### Input example (copy and paste)

```json
{
  "items": [
    {"id": 3, "name": " Wool Socks ", "price": "24", "stock": 150, "tags": ["wool"], "seller": {"name": "Nordic Gear", "country": "NO"}, "created": "2025-02-20T08:15:00Z"},
    {"id": 6, "name": "Rain Jacket", "price": "129", "stock": 5, "tags": ["clothing"], "seller": {"name": "Alpine Ltd", "country": "CH"}, "created": "2025-02-05T07:20:00Z"}
  ],
  "operations": [
    {"op": "flatten", "delimiter": "_", "arrays": "join"},
    {"op": "text", "fields": ["name"], "action": "trim"},
    {"op": "cast", "types": {"price": "number"}},
    {"op": "rename", "mapping": {"seller_name": "seller", "seller_country": "country"}},
    {"op": "filter", "where": {"field": "stock", "op": "gt", "value": 0}},
    {"op": "add_column", "column": "price_with_tax", "expr": "round(price * 1.2, 2)"},
    {"op": "sort", "by": ["price:desc"]}
  ],
  "exports": ["csv"]
}
```

#### Output example (from a real local run of the prefill)

```json
{
  "id": 6,
  "name": "Rain Jacket",
  "price": 129,
  "price_with_tax": 154.8,
  "seller": "Alpine Ltd",
  "country": "CH",
  "tags": "clothing",
  "year": 2025
}
```

### Before and after (three real local runs)

**1. Flatten nested JSON and rename headers.** Steps: `flatten` (delimiter `_`, arrays by index), `rename` with style `snake_case`, `drop` empty values.

Before:

```json
{"id": "u1", "User Name": "Ann Lee", "profile": {"email": "ann@example.com", "address": {"city": "Oslo", "zip": "0150"}}, "orders": [{"sku": "A1", "qty": 2}, {"sku": "B7", "qty": 1}]}
```

After:

```json
{"id": "u1", "name": "Ann Lee", "profile_email": "ann@example.com", "profile_address_city": "Oslo", "orders_0_sku": "A1", "orders_0_qty": 2, "orders_1_sku": "B7", "orders_1_qty": 1}
```

**2. Filter with AND/OR rules, sort naturally, number the rows, compute years.** Six CSV rows in, five out (`Room 10` sorts after `Room 9`, not after `Room 1`).

Before (first CSV row):

```json
{"employee": "Dana Cole", "room": "Room 10", "department": "Sales", "salary": 52000, "start": "2023-04-01"}
```

After (first two output rows):

```json
[{"row": 1, "employee": "Fay Ross", "room": "Room 1", "department": "Sales", "salary": 61000, "years": 4},
 {"row": 2, "employee": "Eli Park", "room": "Room 2", "department": "Support", "salary": 41000, "years": 2.3}]
```

**3. Split, fill, cast and explode.** Three orders in, five rows out (one per item). The text `n/a` in `total` could not become a number: that value became `null` and the run's `SUMMARY` lists it (`column 'total' to number: 'n/a' is not a number`).

Before:

```json
{"order": "1001", "customer": "  Zoe Adams ", "city": "Austin", "items": "pen;ink;pad", "total": "19.50", "paid": "yes"}
```

After (first two rows):

```json
[{"order": 1001, "city": "Austin", "items": "pen", "total": 19.5, "paid": true, "first_name": "Zoe", "last_name": "Adams", "line": 0, "order_label": "ZOE-1001"},
 {"order": 1001, "city": "Austin", "items": "ink", "total": 19.5, "paid": true, "first_name": "Zoe", "last_name": "Adams", "line": 1, "order_label": "ZOE-1001"}]
```

### How to rename columns

```json
{"op": "rename", "mapping": {"User Name": "name", "address.city": "city"}, "style": "snake"}
```

`mapping` renames chosen columns (a dotted nested path becomes a top-level column). `style` (`snake`, `kebab`, `camel`, `pascal`, `lower`, `upper`, `title`) restyles all other columns; `prefix` and `suffix` are also available. Use `reorder` (`{"op": "reorder", "fields": ["id", "name"], "rest": "alphabetical"}`) to change the order, `select` to keep columns (`{"field": "b.c", "as": "c"}` renames while selecting, `*` is a wildcard) and `drop` to remove them.

### How to flatten JSON

```json
{"op": "flatten", "delimiter": "_", "arrays": "index", "maxDepth": 10}
```

Nested objects become `parent_child` columns. `arrays` is `keep` (default, leave lists), `join` (scalars joined with `joinWith`, default `, `) or `index` (`orders_0_sku`; lists longer than `maxArrayItems`, default 50, are kept as lists and noted in the SUMMARY). `unflatten` does the reverse, turning `a.b.0` columns back into nested objects and lists. To get one row per list item use `explode`.

### How to filter a dataset

```json
{"op": "filter", "where": {"any": [
  {"field": "department", "op": "equals", "value": "Sales"},
  {"all": [{"field": "salary", "op": ">", "value": 40000}, {"field": "department", "op": "in", "value": ["Support", "Legal"]}]}
]}}
```

Conditions: `equals`, `notEquals`, `contains`, `notContains`, `startsWith`, `endsWith`, `regex`, `gt` (`>`), `gte`, `lt` (`<`), `lte`, `between` (`[min, max]`), `in` (list), `exists`, `notExists`, `empty` (`is empty`), `notEmpty`. Combine with `all`, `any` and `not`, nested up to 8 levels. `"action": "remove"` deletes the matching rows instead of keeping them. Matching ignores case unless `"caseSensitive": true`.

### All steps

| Step | What it does |
|---|---|
| `select`, `drop`, `rename`, `reorder` | Choose, remove, rename and order columns |
| `flatten`, `unflatten` | Nested objects and lists to columns and back |
| `filter` | Keep or remove rows by conditions |
| `sort` | Multi-key; `natural: true` for Room 2 before Room 10; numbers, ISO dates and text; empty values last |
| `sample` | `first` N, `random` N or `fraction` (with `seed`, default 42), `every` Nth (with `offset`) |
| `limit`, `skip` | First N rows, skip N rows |
| `add_column` | `expr`, a constant `value`, or `rowNumber` (`start`, `step`) |
| `text` | `trim`, `lower`, `upper`, `title`, `collapse_spaces`, `replace`, `regex_replace` on chosen columns |
| `cast` | `string`, `integer`, `number`, `boolean`, `date`, `datetime`, `json` |
| `fill` | Empty cells to a constant, or the value from the row above (`previous`) |
| `split` | One column into several by a delimiter (`into` names or `maxParts`) |
| `explode` | A list (or delimited text) into one row per item; several columns can be zipped |

Unknown steps or settings are rejected before any row is charged, with a hint such as `Did you mean 'sort'?`.

#### Computed columns: the expression language

Examples: `price * qty`, `first & " " & last`, `{Unit Price} * 1.2` (braces for names with spaces), `if(stock > 0, "yes", "no")`, `date_part(created, "year")`, `round(a / b, 2)`. Operators `+ - * / % **`, `&` (text join), comparisons, `and`, `or`, `not`. Functions: concat, upper, lower, trim, title, slug, collapse_spaces, replace, regex_replace, regex_extract, substr, left, right, split_part, len, join, contains, starts_with, ends_with, pad_left, number, text, round, abs, floor, ceil, sqrt, min, max, sum, coalesce, if, is_empty, date_part (year, month, day, quarter, week, weekday, weekdayname, date, epoch ...), date_format, days_between, add_days. This is a hand-written parser with a fixed list of functions. Python's eval is never used, there is no attribute access, no imports and no loops.

### Inputs

| Field | Default | Meaning |
|---|---|---|
| `operations` (required) | - | The ordered steps |
| `items` | - | Pasted JSON rows |
| `datasetIds`, `datasetUrls` | - | Apify datasets (picker, IDs, links) |
| `fileUrls` | - | CSV, TSV, JSON, JSONL, XLSX links |
| `inlineSources` | - | Pasted CSV/JSON/JSONL text |
| `onError` | `null` | A value that cannot be computed or cast becomes null, or `dropRow` |
| `exports` | none | `csv`, `xlsx`, `json`, `jsonl` |
| `exportName` | `transformed` | File base name |
| `escapeFormulas` | true | Protect CSV from `=`/`+`/`-`/`@` formula injection |
| `maxItems` | 100000 | Input rows read, across all sources |
| `maxOutputRows` | 1000000 | Safety cap for `explode` |
| `maxFileSizeMb` | 50 | File download cap |
| `csvInferTypes` | true | Numbers and booleans from CSV text |
| `xlsxSheet` | first | Sheet name or number |

### How much does it cost

Pay per event: **$0.0017 per input row** (`row-transformed`, $1.70 per 1,000) and **$0.004 per export file** (`export-file`). You pay for rows read, not rows written, so a filter or `explode` does not change the price. Rows are charged in chunks of up to 500 (50 when the pipeline has a `limit` or `sample` step) before they are processed; if your spending limit runs out the run stops with what was paid for.

| Job | Cost |
|---|---|
| 1,000 rows, dataset only | $1.70 |
| 10,000 rows + CSV | $17.004 |
| 100,000 rows + CSV and Excel | $170.008 |

An invalid pipeline or an empty source costs nothing.

### Use cases

- Flatten scraper output (nested JSON) into spreadsheet columns before export.
- Rename and restyle columns so two scrapers' datasets use the same field names.
- Keep only the rows you need (price range, country list, text patterns) and sort them.
- Take a reproducible random sample or every Nth row for QA.
- Split `"Last, First"` columns, explode tag lists into rows, cast text to numbers and dates.

### FAQ

**Does it run code?** No. The pipeline is JSON; expressions go through a fixed parser and function list.

**What if one value is bad?** By default it becomes null and is counted in `SUMMARY.valueErrors` with up to five examples; set `onError` to `dropRow` to drop such rows. A bad source never stops the other sources.

**How big can it go?** Streaming steps use little memory. `sort` and random `sample` hold the surviving rows in memory (bounded by `maxItems`). A JSON array file is limited to 100 MB; use JSONL or CSV for bigger files.

**Does a limit step save money?** Yes. When a `limit` or first-N `sample` is full and no `sort` or random sample comes before it, reading stops. Up to 49 extra rows may have been charged already.

**Can it read a dataset from another account?** Only if your run can read it; pick it in `Apify datasets`. Links to localhost and private networks are blocked for file URLs.

**Does it join two datasets?** No. Several sources are processed one after another through the same pipeline.

### Use with the Apify API, MCP, n8n and Make

Python:

```python
from apify_client import ApifyClient
run = ApifyClient("<TOKEN>").actor("<username>/dataset-transform").call(run_input={
    "datasetIds": ["<DATASET_ID>"],
    "operations": [{"op": "flatten", "delimiter": "_"}, {"op": "sort", "by": ["price:desc"]}],
})
```

JavaScript:

```js
import { ApifyClient } from 'apify-client';
const run = await new ApifyClient({ token: '<TOKEN>' }).actor('<username>/dataset-transform').call({
  items: [{ a: { b: 1 } }],
  operations: [{ op: 'flatten', delimiter: '_' }],
});
```

In n8n and Make use the Apify node/module "Run Actor" with the same JSON input. MCP clients can call it through the Apify MCP server; the only required field is `operations`.

### Related tools

- Merge Datasets & Dedupe: Combine, Filter, Export CSV & Excel
- Remove Duplicates: Deduplicate Datasets, CSV & Excel
- Excel to JSON Converter: XLSX, XLS & CSV to JSON API

# Actor input Schema

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

Rows pasted straight into the input, as a JSON array of objects. Nested objects and lists are fine. Example: \[{"name": "Ann", "address": {"city": "Oslo"}}]. Works offline, so it is the quickest way to try a pipeline. Leave empty when you use datasets or files.

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

Datasets to transform. Pick them from the list, or paste dataset IDs. Reads go through your run, so datasets of other Actors and runs that you can read work. Example: \["aBcD1234eFgH5678i", "username~my-named-dataset"].

## `datasetUrls` (type: `array`):

Dataset IDs, "username~name" names or Apify links, one per line. Example: https://console.apify.com/storage/datasets/aBcD1234eFgH5678i. A dataset that is only given here may be unreadable when the run has limited permissions: pick it in "Apify datasets" instead.

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

Direct links to .csv, .tsv, .json, .jsonl or .xlsx files, one per line. The type is detected from the extension or the content. Example: https://example.com/export/products.csv. Links to localhost and private networks are blocked.

## `inlineSources` (type: `array`):

CSV, JSON or JSONL text pasted into the input. Each item is {"name": ..., "format": "csv" | "json" | "jsonl", "text": ...}. Example: \[{"name": "leads", "format": "csv", "text": "email,name\na@x.com,Ann"}].

## `operations` (type: `array`):

The steps to run, in order, as JSON data (no code). Each step is {"op": ..., settings}. Operations: select, drop, rename, reorder, flatten, unflatten, filter, sort, sample, limit, skip, add_column, text, cast, fill, split, explode. Example: \[{"op": "rename", "mapping": {"old": "new"}}, {"op": "sort", "by": \["price:desc"]}]. Unknown steps or settings are rejected with a clear message before any row is charged.

## `onError` (type: `string`):

null: set that value to null and count the problem in the SUMMARY record. dropRow: leave out the whole row. A step can override this with its own onError. Example: null.

## `exports` (type: `array`):

Also write the result as downloadable files in the key-value store, each with a link in the SUMMARY record. Every file is charged once. Leave empty for the dataset only. Example: \["csv", "xlsx"].

## `exportName` (type: `string`):

Base name of the export files, e.g. transformed gives transformed.csv and transformed.xlsx.

## `escapeFormulas` (type: `boolean`):

In the CSV export, text that starts with = + - or @ gets a leading apostrophe so Excel and Sheets cannot run it as a formula. Excel files always store such text as plain text. Turn off only if you need the raw values.

## `maxItems` (type: `integer`):

Hard cap on input rows read across all sources, in rows. You are charged per input row read, so this also caps cost. Example: 10000. It also bounds memory for sort and random sample.

## `maxOutputRows` (type: `integer`):

Stop writing after this many output rows. A safety cap for the explode step, which can turn one row into many. Example: 500000.

## `maxFileSizeMb` (type: `integer`):

Files larger than this are rejected (that source fails, the others continue). A JSON array file is loaded whole and is limited to 100 MB; use JSONL or CSV for bigger files. Example: 50.

## `csvInferTypes` (type: `boolean`):

Turn CSV text like 42, 3.5 or true into numbers and booleans. Values with leading zeros (007), plus signs or very long digit strings stay text.

## `xlsxSheet` (type: `string`):

Sheet name or number (1 = first) to read from every Excel file. Leave empty for the first sheet. Example: Sheet2.

## Actor input object example

```json
{
  "items": [
    {
      "id": 1,
      "name": "  Trail Backpack ",
      "price": "89.90",
      "stock": 12,
      "tags": [
        "outdoor",
        "bag"
      ],
      "seller": {
        "name": "Nordic Gear",
        "country": "NO"
      },
      "created": "2025-03-14T09:30:00Z"
    },
    {
      "id": 2,
      "name": "Camp Stove",
      "price": "$45.00",
      "stock": 0,
      "tags": [
        "outdoor",
        "cooking"
      ],
      "seller": {
        "name": "Fire & Co",
        "country": "US"
      },
      "created": "2025-01-02T10:00:00Z"
    },
    {
      "id": 3,
      "name": "Wool Socks 10-pack",
      "price": "24",
      "stock": 150,
      "tags": [],
      "seller": {
        "name": "Nordic Gear",
        "country": "NO"
      },
      "created": "2025-02-20T08:15:00Z"
    },
    {
      "id": 4,
      "name": "Headlamp 300",
      "price": "39.5",
      "stock": 8,
      "tags": [
        "light"
      ],
      "seller": {
        "name": "Fire & Co",
        "country": "US"
      },
      "created": "2024-11-30T17:45:00Z"
    },
    {
      "id": 5,
      "name": " Water Bottle 1L",
      "price": "12",
      "stock": 40,
      "tags": [
        "bottle",
        "steel"
      ],
      "seller": {
        "name": "Nordic Gear",
        "country": "NO"
      },
      "created": "2025-03-01T12:00:00Z"
    },
    {
      "id": 6,
      "name": "Rain Jacket",
      "price": "129",
      "stock": 5,
      "tags": [
        "clothing"
      ],
      "seller": {
        "name": "Alpine Ltd",
        "country": "CH"
      },
      "created": "2025-02-05T07:20:00Z"
    }
  ],
  "operations": [
    {
      "op": "flatten",
      "delimiter": "_",
      "arrays": "join"
    },
    {
      "op": "text",
      "fields": [
        "name"
      ],
      "action": "trim"
    },
    {
      "op": "cast",
      "types": {
        "price": "number"
      }
    },
    {
      "op": "rename",
      "mapping": {
        "seller_name": "seller",
        "seller_country": "country"
      }
    },
    {
      "op": "filter",
      "where": {
        "field": "stock",
        "op": "gt",
        "value": 0
      }
    },
    {
      "op": "add_column",
      "column": "price_with_tax",
      "expr": "round(price * 1.2, 2)"
    },
    {
      "op": "add_column",
      "column": "year",
      "expr": "date_part(created, 'year')"
    },
    {
      "op": "sort",
      "by": [
        "price:desc"
      ]
    },
    {
      "op": "select",
      "fields": [
        "id",
        "name",
        "price",
        "price_with_tax",
        "seller",
        "country",
        "tags",
        "year"
      ]
    }
  ],
  "onError": "null",
  "exports": [
    "csv"
  ],
  "exportName": "transformed",
  "escapeFormulas": true,
  "maxItems": 100000,
  "maxOutputRows": 1000000,
  "maxFileSizeMb": 50,
  "csvInferTypes": true
}
```

# Actor output Schema

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

The transformed rows (all result items of this run, as JSON, CSV, Excel and more).

## `records` (type: `string`):

Key-value store records: SUMMARY (rows in and out, per-step counts, value problems, source errors, export links) and the generated export files.

# 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": [
        {
            "id": 1,
            "name": "  Trail Backpack ",
            "price": "89.90",
            "stock": 12,
            "tags": [
                "outdoor",
                "bag"
            ],
            "seller": {
                "name": "Nordic Gear",
                "country": "NO"
            },
            "created": "2025-03-14T09:30:00Z"
        },
        {
            "id": 2,
            "name": "Camp Stove",
            "price": "$45.00",
            "stock": 0,
            "tags": [
                "outdoor",
                "cooking"
            ],
            "seller": {
                "name": "Fire & Co",
                "country": "US"
            },
            "created": "2025-01-02T10:00:00Z"
        },
        {
            "id": 3,
            "name": "Wool Socks 10-pack",
            "price": "24",
            "stock": 150,
            "tags": [],
            "seller": {
                "name": "Nordic Gear",
                "country": "NO"
            },
            "created": "2025-02-20T08:15:00Z"
        },
        {
            "id": 4,
            "name": "Headlamp 300",
            "price": "39.5",
            "stock": 8,
            "tags": [
                "light"
            ],
            "seller": {
                "name": "Fire & Co",
                "country": "US"
            },
            "created": "2024-11-30T17:45:00Z"
        },
        {
            "id": 5,
            "name": " Water Bottle 1L",
            "price": "12",
            "stock": 40,
            "tags": [
                "bottle",
                "steel"
            ],
            "seller": {
                "name": "Nordic Gear",
                "country": "NO"
            },
            "created": "2025-03-01T12:00:00Z"
        },
        {
            "id": 6,
            "name": "Rain Jacket",
            "price": "129",
            "stock": 5,
            "tags": [
                "clothing"
            ],
            "seller": {
                "name": "Alpine Ltd",
                "country": "CH"
            },
            "created": "2025-02-05T07:20:00Z"
        }
    ],
    "operations": [
        {
            "op": "flatten",
            "delimiter": "_",
            "arrays": "join"
        },
        {
            "op": "text",
            "fields": [
                "name"
            ],
            "action": "trim"
        },
        {
            "op": "cast",
            "types": {
                "price": "number"
            }
        },
        {
            "op": "rename",
            "mapping": {
                "seller_name": "seller",
                "seller_country": "country"
            }
        },
        {
            "op": "filter",
            "where": {
                "field": "stock",
                "op": "gt",
                "value": 0
            }
        },
        {
            "op": "add_column",
            "column": "price_with_tax",
            "expr": "round(price * 1.2, 2)"
        },
        {
            "op": "add_column",
            "column": "year",
            "expr": "date_part(created, 'year')"
        },
        {
            "op": "sort",
            "by": [
                "price:desc"
            ]
        },
        {
            "op": "select",
            "fields": [
                "id",
                "name",
                "price",
                "price_with_tax",
                "seller",
                "country",
                "tags",
                "year"
            ]
        }
    ],
    "exports": [
        "csv"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("data-done-right/dataset-transform").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": [
        {
            "id": 1,
            "name": "  Trail Backpack ",
            "price": "89.90",
            "stock": 12,
            "tags": [
                "outdoor",
                "bag",
            ],
            "seller": {
                "name": "Nordic Gear",
                "country": "NO",
            },
            "created": "2025-03-14T09:30:00Z",
        },
        {
            "id": 2,
            "name": "Camp Stove",
            "price": "$45.00",
            "stock": 0,
            "tags": [
                "outdoor",
                "cooking",
            ],
            "seller": {
                "name": "Fire & Co",
                "country": "US",
            },
            "created": "2025-01-02T10:00:00Z",
        },
        {
            "id": 3,
            "name": "Wool Socks 10-pack",
            "price": "24",
            "stock": 150,
            "tags": [],
            "seller": {
                "name": "Nordic Gear",
                "country": "NO",
            },
            "created": "2025-02-20T08:15:00Z",
        },
        {
            "id": 4,
            "name": "Headlamp 300",
            "price": "39.5",
            "stock": 8,
            "tags": ["light"],
            "seller": {
                "name": "Fire & Co",
                "country": "US",
            },
            "created": "2024-11-30T17:45:00Z",
        },
        {
            "id": 5,
            "name": " Water Bottle 1L",
            "price": "12",
            "stock": 40,
            "tags": [
                "bottle",
                "steel",
            ],
            "seller": {
                "name": "Nordic Gear",
                "country": "NO",
            },
            "created": "2025-03-01T12:00:00Z",
        },
        {
            "id": 6,
            "name": "Rain Jacket",
            "price": "129",
            "stock": 5,
            "tags": ["clothing"],
            "seller": {
                "name": "Alpine Ltd",
                "country": "CH",
            },
            "created": "2025-02-05T07:20:00Z",
        },
    ],
    "operations": [
        {
            "op": "flatten",
            "delimiter": "_",
            "arrays": "join",
        },
        {
            "op": "text",
            "fields": ["name"],
            "action": "trim",
        },
        {
            "op": "cast",
            "types": { "price": "number" },
        },
        {
            "op": "rename",
            "mapping": {
                "seller_name": "seller",
                "seller_country": "country",
            },
        },
        {
            "op": "filter",
            "where": {
                "field": "stock",
                "op": "gt",
                "value": 0,
            },
        },
        {
            "op": "add_column",
            "column": "price_with_tax",
            "expr": "round(price * 1.2, 2)",
        },
        {
            "op": "add_column",
            "column": "year",
            "expr": "date_part(created, 'year')",
        },
        {
            "op": "sort",
            "by": ["price:desc"],
        },
        {
            "op": "select",
            "fields": [
                "id",
                "name",
                "price",
                "price_with_tax",
                "seller",
                "country",
                "tags",
                "year",
            ],
        },
    ],
    "exports": ["csv"],
}

# Run the Actor and wait for it to finish
run = client.actor("data-done-right/dataset-transform").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": [
    {
      "id": 1,
      "name": "  Trail Backpack ",
      "price": "89.90",
      "stock": 12,
      "tags": [
        "outdoor",
        "bag"
      ],
      "seller": {
        "name": "Nordic Gear",
        "country": "NO"
      },
      "created": "2025-03-14T09:30:00Z"
    },
    {
      "id": 2,
      "name": "Camp Stove",
      "price": "$45.00",
      "stock": 0,
      "tags": [
        "outdoor",
        "cooking"
      ],
      "seller": {
        "name": "Fire & Co",
        "country": "US"
      },
      "created": "2025-01-02T10:00:00Z"
    },
    {
      "id": 3,
      "name": "Wool Socks 10-pack",
      "price": "24",
      "stock": 150,
      "tags": [],
      "seller": {
        "name": "Nordic Gear",
        "country": "NO"
      },
      "created": "2025-02-20T08:15:00Z"
    },
    {
      "id": 4,
      "name": "Headlamp 300",
      "price": "39.5",
      "stock": 8,
      "tags": [
        "light"
      ],
      "seller": {
        "name": "Fire & Co",
        "country": "US"
      },
      "created": "2024-11-30T17:45:00Z"
    },
    {
      "id": 5,
      "name": " Water Bottle 1L",
      "price": "12",
      "stock": 40,
      "tags": [
        "bottle",
        "steel"
      ],
      "seller": {
        "name": "Nordic Gear",
        "country": "NO"
      },
      "created": "2025-03-01T12:00:00Z"
    },
    {
      "id": 6,
      "name": "Rain Jacket",
      "price": "129",
      "stock": 5,
      "tags": [
        "clothing"
      ],
      "seller": {
        "name": "Alpine Ltd",
        "country": "CH"
      },
      "created": "2025-02-05T07:20:00Z"
    }
  ],
  "operations": [
    {
      "op": "flatten",
      "delimiter": "_",
      "arrays": "join"
    },
    {
      "op": "text",
      "fields": [
        "name"
      ],
      "action": "trim"
    },
    {
      "op": "cast",
      "types": {
        "price": "number"
      }
    },
    {
      "op": "rename",
      "mapping": {
        "seller_name": "seller",
        "seller_country": "country"
      }
    },
    {
      "op": "filter",
      "where": {
        "field": "stock",
        "op": "gt",
        "value": 0
      }
    },
    {
      "op": "add_column",
      "column": "price_with_tax",
      "expr": "round(price * 1.2, 2)"
    },
    {
      "op": "add_column",
      "column": "year",
      "expr": "date_part(created, '\''year'\'')"
    },
    {
      "op": "sort",
      "by": [
        "price:desc"
      ]
    },
    {
      "op": "select",
      "fields": [
        "id",
        "name",
        "price",
        "price_with_tax",
        "seller",
        "country",
        "tags",
        "year"
      ]
    }
  ],
  "exports": [
    "csv"
  ]
}' |
apify call data-done-right/dataset-transform --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,data-done-right/dataset-transform"
        }
    }
}
```

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/mEruWvyU9kBL87qhW/builds/qoWIX8gQXwhojL23E/openapi.json
