Edit

Change a CSV in its columns rows splits and transpose

POST/v1/edit/csv/transform

View as Markdown

Runs a list of steps on CSV text, one after the other in the order listed: keep, reorder, drop or rename columns; drop the first or last rows or rows by number (counted from 1); filter rows by a column condition; split a column into several; turn a cell into several rows; transpose. Quoted commas and line breaks survive every step, and the output separator and line ending are options.

Request body

Name Type Required Description
content string yes The CSV text
steps array of CsvTransformStep yes What to do, run one after the other in the order listed
delimiter string no The separator of the input (default ",")
has_header boolean no False names the columns column_1, column_2 and writes no header row (default true)
output_delimiter string no The separator of the output. Default: same as the input
quoting one of: minimal, all no minimal or all (default "minimal")
line_ending one of: crlf, lf no crlf or lf (default "crlf")
byte_order_mark boolean no True starts the output with a byte order mark (default false)

Example request

A request body that works, as JSON.

{
  "content": "name,city,email\nAna,Porto,ana@example.com\nRui,Lisboa,rui@example.com\n",
  "steps": [
    {
      "operation": "select_columns",
      "columns": [
        "name",
        "city"
      ]
    },
    {
      "operation": "rename_columns",
      "mapping": {
        "name": "customer"
      }
    },
    {
      "operation": "filter_rows",
      "column": "city",
      "operator": "equals",
      "value": "Porto"
    }
  ]
}

Code samples

Replace YOUR_TOKEN with your access token.

curl

curl -X POST 'https://api.skillfulsardine.com/v1/edit/csv/transform' \
  -H 'Authorization: Bearer YOUR_TOKEN' \
  -H 'Content-Type: application/json' \
  --data-raw '{
  "content": "name,city,email\nAna,Porto,ana@example.com\nRui,Lisboa,rui@example.com\n",
  "steps": [
    {
      "operation": "select_columns",
      "columns": [
        "name",
        "city"
      ]
    },
    {
      "operation": "rename_columns",
      "mapping": {
        "name": "customer"
      }
    },
    {
      "operation": "filter_rows",
      "column": "city",
      "operator": "equals",
      "value": "Porto"
    }
  ]
}'

PowerShell

$body = @'
{
  "content": "name,city,email\nAna,Porto,ana@example.com\nRui,Lisboa,rui@example.com\n",
  "steps": [
    {
      "operation": "select_columns",
      "columns": [
        "name",
        "city"
      ]
    },
    {
      "operation": "rename_columns",
      "mapping": {
        "name": "customer"
      }
    },
    {
      "operation": "filter_rows",
      "column": "city",
      "operator": "equals",
      "value": "Porto"
    }
  ]
}
'@
$response = Invoke-RestMethod -Method Post -Uri 'https://api.skillfulsardine.com/v1/edit/csv/transform' `
  -Headers @{ Authorization = 'Bearer YOUR_TOKEN' } `
  -ContentType 'application/json; charset=utf-8' `
  -Body ([System.Text.Encoding]::UTF8.GetBytes($body))
$response | ConvertTo-Json -Depth 10

Python

import requests

payload = {
    "content": "name,city,email\nAna,Porto,ana@example.com\nRui,Lisboa,rui@example.com\n",
    "steps": [
        {
            "operation": "select_columns",
            "columns": [
                "name",
                "city",
            ],
        },
        {
            "operation": "rename_columns",
            "mapping": {
                "name": "customer",
            },
        },
        {
            "operation": "filter_rows",
            "column": "city",
            "operator": "equals",
            "value": "Porto",
        },
    ],
}

response = requests.post(
    "https://api.skillfulsardine.com/v1/edit/csv/transform",
    headers={"Authorization": "Bearer YOUR_TOKEN"},
    json=payload,
    timeout=30,
)
print(response.json())

JavaScript

const response = await fetch("https://api.skillfulsardine.com/v1/edit/csv/transform", {
  method: "POST",
  headers: {
    "Authorization": "Bearer YOUR_TOKEN",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
  "content": "name,city,email\nAna,Porto,ana@example.com\nRui,Lisboa,rui@example.com\n",
  "steps": [
    {
      "operation": "select_columns",
      "columns": [
        "name",
        "city"
      ]
    },
    {
      "operation": "rename_columns",
      "mapping": {
        "name": "customer"
      }
    },
    {
      "operation": "filter_rows",
      "column": "city",
      "operator": "equals",
      "value": "Porto"
    }
  ]
}),
});
const result = await response.json();
console.log(result);

Node

// Node 18 or later. Save as call.mjs and run: API_TOKEN=... node call.mjs
const token = process.env.API_TOKEN;

const response = await fetch("https://api.skillfulsardine.com/v1/edit/csv/transform", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${token}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
  "content": "name,city,email\nAna,Porto,ana@example.com\nRui,Lisboa,rui@example.com\n",
  "steps": [
    {
      "operation": "select_columns",
      "columns": [
        "name",
        "city"
      ]
    },
    {
      "operation": "rename_columns",
      "mapping": {
        "name": "customer"
      }
    },
    {
      "operation": "filter_rows",
      "column": "city",
      "operator": "equals",
      "value": "Porto"
    }
  ]
}),
});
const result = await response.json();
console.log(result);

Rust

// Cargo.toml: reqwest = { version = "0.12", features = ["blocking", "json"] }, serde_json = "1"
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let body: serde_json::Value = serde_json::from_str(r#"{
  "content": "name,city,email\nAna,Porto,ana@example.com\nRui,Lisboa,rui@example.com\n",
  "steps": [
    {
      "operation": "select_columns",
      "columns": [
        "name",
        "city"
      ]
    },
    {
      "operation": "rename_columns",
      "mapping": {
        "name": "customer"
      }
    },
    {
      "operation": "filter_rows",
      "column": "city",
      "operator": "equals",
      "value": "Porto"
    }
  ]
}"#)?;
    let response = reqwest::blocking::Client::new()
        .post("https://api.skillfulsardine.com/v1/edit/csv/transform")
        .bearer_auth("YOUR_TOKEN")
        .json(&body)
        .send()?;
    println!("{}", response.text()?);
    Ok(())
}

Swift

import Foundation

var request = URLRequest(url: URL(string: "https://api.skillfulsardine.com/v1/edit/csv/transform")!)
request.httpMethod = "POST"
request.setValue("Bearer YOUR_TOKEN", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = Data(#"""
{
  "content": "name,city,email\nAna,Porto,ana@example.com\nRui,Lisboa,rui@example.com\n",
  "steps": [
    {
      "operation": "select_columns",
      "columns": [
        "name",
        "city"
      ]
    },
    {
      "operation": "rename_columns",
      "mapping": {
        "name": "customer"
      }
    },
    {
      "operation": "filter_rows",
      "column": "city",
      "operator": "equals",
      "value": "Porto"
    }
  ]
}
"""#.utf8)

let (data, _) = try await URLSession.shared.data(for: request)
print(String(decoding: data, as: UTF8.self))

Response

The fields inside data of a successful response.

Name Type Required Description
content string yes The CSV text after every step
headers array of string yes The column names after every step
rows array of array of string yes The data rows after every step
row_count integer yes Number of data rows
column_count integer yes Number of columns

Example response

A real answer from the operation, as JSON. The fields in the table above are inside data.

{
  "data": {
    "content": "customer,city\r\nAna,Porto\r\n",
    "headers": [
      "customer",
      "city"
    ],
    "rows": [
      [
        "Ana",
        "Porto"
      ]
    ],
    "row_count": 1,
    "column_count": 2
  },
  "errors": null,
  "meta": {
    "request_id": "741debbd-9d83-4b2e-90fa-14817291e911",
    "timestamp": "2026-10-27T07:19:42.178363Z",
    "version": "2026-03-26"
  },
  "pagination": null
}

Responses

Status When it happens
200 Successful Response
400 The body respects the operation schema, but the use case rejected it. Each problem is one ApiError entry
413 The request is larger than the request size limit of the API allows
422 The body does not respect the operation schema. Each problem is one ApiError entry with the code validation_error
500 Something failed inside the server and was not an input problem. The same envelope, errors may be empty