Skip to content

Data formats

Who this is for: people and AI assistants who read or write XML, CSV, NDJSON, Excel workbooks, fixed-width records, form data, multipart bodies, base64, hex or older text encodings in a workflow.

Before you start: know where data formats show up: ctx.formats in scripts, the xml.parse, xml.build, xml.split, csv.parse, xlsx.read, xlsx.write, fixed.parse and fixed.build actions, and the parse option of a webhook trigger.

This page lists every data format operation, the options it takes, its default limits and the errors it can give. It is generated from the code, so it always matches what Liitora does. AI assistants can read the same list as the MCP resource liitora://reference/data-formats.

Scripts, built-in actions and webhook triggers all use the same readers and writers. The same data gives the same result in a run: script, in platform/xml.parse and in a webhook with parse: xml.

Data from outside is read inside the sandbox, never by the Liitora worker itself. Each piece of data gets its own short-lived sandbox with no network, no files and no access to your secrets. It has its own memory, CPU and time limits. If data from one sender tries to attack the reader, it can’t reach anything else.

The readers are strict. They refuse data that two systems could read in different ways, such as XML with a DOCTYPE, a CSV row with a missing field or text that is not valid in its encoding. Refusing is safer than guessing.

Liitora reads and writes .xlsx workbooks. It never reads macro workbooks (.xlsm), binary workbooks (.xlsb) or the old .xls format, so a file that could carry code is refused instead of opened.

  • Formulas are never run. A formula cell gives the value Excel saved with it the last time the file was saved, and the result’s formulas says how many formula cells there were. If a value looks stale, open and save the file in Excel, or compute the value in the workflow instead.
  • Text that looks like a formula is written as text. When Liitora writes a workbook, a cell that starts with =, +, -, @, a tab or a carriage return (also after leading spaces, and the full-width signs such as =) gets a leading ', the same rule as for CSV files. This stops a value from your data from running as a formula when someone opens the file. Numbers are never changed.
  • Dates follow the workbook. Excel stores dates as numbers counted from 1900 or, in some older Mac files, from 1904. Liitora reads which one the file uses (the result’s epoch) and turns date cells into ISO 8601 text such as 2026-10-04 or 2026-10-04T08:30:00. Set dates: "serial" to get the numbers instead.
  • Zip bombs are refused. A workbook is a zip file. Liitora limits how many files it holds, how large they unpack and how much one file may grow when unpacked, and refuses a workbook that holds another archive.

Fixed-width files, common for bank and payroll exchanges, give every field a set position and width on each line. You describe the layout as a list of fields and say with unit whether positions count characters ("chars") or bytes in the file’s encoding ("bytes"). Use "bytes" when the file’s specification counts bytes and the file has letters such as ä or ö that take more than one byte.

Liitora never cuts or guesses silently. Overlapping fields are refused. A value too long for its field fails the step instead of being cut. A line shorter or longer than the layout fails too, unless you ask for padding (short_lines in a step, shortLines in a script) or for the extra to be dropped (long_lines or longLines). Empty lines are skipped.

When data can’t be read or written:

  • In a script, the call throws an Error with name FormatError and code FORMAT_ERROR. The message says what is wrong (see Error kinds) and usually names the line. Catch it to handle bad data yourself.
  • In a built-in action or a webhook trigger, and in a script that doesn’t catch the error, the step fails with format_error.

A format_error is never retried, whatever the step’s retry says, because the same data would fail the same way. Fix the data where it comes from, then run the workflow again.

If the sandbox that reads the data can’t start, the step fails with codec_unavailable. That is a problem on our side, not in your data. A step whose retry includes network tries again.

The limits below are the defaults for scripts. Built-in actions can have their own, stricter or larger, limits, listed with each action in Built-in actions. For example, csv.parse reads up to 50 MB. A built-in step that needs more memory, CPU or time than it is allowed fails with format_error too, with a message that says so.

ctx.formats.xml.parse(text, options?)

  • Takes: XML text
  • Gives: an object with one key, the root element

Options:

Option Type Default What it does
arrays "always" | string[] only repeated elements Element paths from the root (such as “order.items.item”) that are always arrays, or “always” for every child element.
namespaces "strip" | "prefix" | "uri" “strip” How namespaced names appear: local names only, names as written, or {uri}local.
charset string “utf-8” The charset the text was decoded from. An encoding in the XML declaration must match it.

Limits:

Limit Default What it limits
max_input_bytes 8 MB Largest input, in bytes.
max_depth 64 Deepest element nesting.
max_nodes 200,000 Most elements, attributes and text pieces.
max_attributes 256 Most attributes on one element.
max_name_bytes 1,024 Longest element or attribute name, in bytes.
max_output_bytes 16 MB Largest result, in bytes.

ctx.formats.xml.build(value, options?)

  • Takes: an object with one key, the root element
  • Gives: XML text

Options:

Option Type Default What it does
declaration boolean true Write the XML declaration first.

Limits:

Limit Default What it limits
max_input_bytes 8 MB Largest input, in bytes.
max_depth 64 Deepest element nesting.
max_nodes 200,000 Most elements, attributes and text pieces.
max_attributes 256 Most attributes on one element.
max_name_bytes 1,024 Longest element or attribute name, in bytes.
max_output_bytes 16 MB Largest result, in bytes.

ctx.formats.csv.parse(text, options?)

  • Takes: CSV text or a Uint8Array of UTF-8
  • Gives: an array of objects, or of arrays without a header

Options:

Option Type Default What it does
delimiter string “,” One ASCII character between fields.
quote string ‘“’ One ASCII character that quotes a field.
header boolean true The first row names the columns and every row becomes an object. Otherwise every row is an array of strings.
trim boolean false Remove spaces and tabs around every field and header name.

Limits:

Limit Default What it limits
max_input_bytes 8 MB Largest input, in bytes.
max_rows 200,000 Most data rows.
max_columns 2,000 Most fields in one row.
max_field_bytes 1 MB Largest single field, in bytes.
max_output_bytes 16 MB Largest result, in bytes.

ctx.formats.csv.build(rows, options?)

  • Takes: an array of objects, or of arrays without a header
  • Gives: CSV text

Options:

Option Type Default What it does
delimiter string “,” One ASCII character between fields.
quote string ‘“’ One ASCII character that quotes a field.
header boolean true Rows are objects and a header row of the first row’s keys is written first. Otherwise rows are arrays.

Limits:

Limit Default What it limits
max_input_bytes 8 MB Largest input, in bytes.
max_rows 200,000 Most data rows.
max_columns 2,000 Most fields in one row.
max_field_bytes 1 MB Largest single field, in bytes.
max_output_bytes 16 MB Largest result, in bytes.

ctx.formats.ndjson.parse(text)

  • Takes: NDJSON text or a Uint8Array of UTF-8
  • Gives: an array with one value per line

Options: none.

Limits:

Limit Default What it limits
max_input_bytes 8 MB Largest input, in bytes.
max_lines 200,000 Most lines.
max_line_bytes 1 MB Longest line, in bytes.
max_depth 64 Deepest nesting in one line.
max_output_bytes 16 MB Largest result, in bytes.

ctx.formats.ndjson.build(values)

  • Takes: an array of values
  • Gives: NDJSON text, one line per value

Options: none.

Limits:

Limit Default What it limits
max_input_bytes 8 MB Largest input, in bytes.
max_lines 200,000 Most lines.
max_line_bytes 1 MB Longest line, in bytes.
max_depth 64 Deepest nesting in one line.
max_output_bytes 16 MB Largest result, in bytes.

ctx.formats.form.parse(text, options?)

  • Takes: application/x-www-form-urlencoded text or a Uint8Array
  • Gives: an object of strings, with arrays for repeated fields

Options:

Option Type Default What it does
duplicates "array" | "error" “array” What a repeated field does: its values become an array, or the parse fails.
arrays "always" | string[] only repeated fields Field names that are always arrays, or “always” for every field.

Limits:

Limit Default What it limits
max_input_bytes 8 MB Largest input, in bytes.
max_fields 10,000 Most fields.
max_name_bytes 1,024 Longest field name, in bytes.
max_output_bytes 16 MB Largest result, in bytes.

ctx.formats.form.build(fields)

  • Takes: an object of strings, numbers, booleans and arrays of those
  • Gives: application/x-www-form-urlencoded text

Options: none.

Limits:

Limit Default What it limits
max_input_bytes 8 MB Largest input, in bytes.
max_fields 10,000 Most fields.
max_name_bytes 1,024 Longest field name, in bytes.
max_output_bytes 16 MB Largest result, in bytes.

ctx.formats.multipart.parse(body, options)

  • Takes: a multipart/form-data body as a Uint8Array or text
  • Gives: an array of parts: name, filename, contentType, headers, data

Options:

Option Type Default What it does
boundary string required The boundary that separates the parts. Give this or contentType.
contentType string required The whole Content-Type header, such as multipart/form-data; boundary=x.

Limits:

Limit Default What it limits
max_input_bytes 16 MB Largest input, in bytes.
max_parts 1,000 Most parts.
max_headers 16 Most header lines in one part.
max_header_bytes 8,192 Largest header block of one part, in bytes.
max_name_bytes 1,024 Longest part name or filename, in bytes.
max_output_bytes 16 MB Largest result, in bytes.

ctx.formats.multipart.build(parts, options?)

  • Takes: an array of parts: name, filename, contentType, data
  • Gives: { body, contentType }

Options:

Option Type Default What it does
boundary string picked from the content The boundary to use. It must not appear in any part’s data.

Limits:

Limit Default What it limits
max_input_bytes 16 MB Largest input, in bytes.
max_parts 1,000 Most parts.
max_headers 16 Most header lines in one part.
max_header_bytes 8,192 Largest header block of one part, in bytes.
max_name_bytes 1,024 Longest part name or filename, in bytes.
max_output_bytes 16 MB Largest result, in bytes.

ctx.formats.text.decode(bytes, options)

  • Takes: a Uint8Array
  • Gives: text, or { text, replaced } with onInvalid: “replace”

Options:

Option Type Default What it does
encoding string required Required. One of utf-8, utf-16le, utf-16be, windows-1252, iso-8859-1, iso-8859-15.
onInvalid "error" | "replace" “error” What a malformed byte sequence does: fail, or become U+FFFD with a count in the result.

Limits:

Limit Default What it limits
max_input_bytes 16 MB Largest input, in bytes.
max_output_bytes 16 MB Largest result, in bytes.

ctx.formats.text.encode(text, options)

  • Takes: text
  • Gives: a Uint8Array

Options:

Option Type Default What it does
encoding string required Required. One of utf-8, utf-16le, utf-16be, windows-1252, iso-8859-1, iso-8859-15.

Limits:

Limit Default What it limits
max_input_bytes 16 MB Largest input, in bytes.
max_output_bytes 16 MB Largest result, in bytes.

ctx.formats.base64.encode(data, options?)

  • Takes: text (as UTF-8) or a Uint8Array
  • Gives: padded base64 text

Options:

Option Type Default What it does
url boolean false Use the URL-safe alphabet (- and _ instead of + and /).

Limits:

Limit Default What it limits
max_input_bytes 16 MB Largest input, in bytes.
max_output_bytes 16 MB Largest result, in bytes.

ctx.formats.base64.decode(text, options?)

  • Takes: base64 text
  • Gives: a Uint8Array

Options:

Option Type Default What it does
url boolean false Use the URL-safe alphabet (- and _ instead of + and /).

Limits:

Limit Default What it limits
max_input_bytes 16 MB Largest input, in bytes.
max_output_bytes 16 MB Largest result, in bytes.

ctx.formats.hex.encode(data)

  • Takes: text (as UTF-8) or a Uint8Array
  • Gives: lower-case hex text

Options: none.

Limits:

Limit Default What it limits
max_input_bytes 16 MB Largest input, in bytes.
max_output_bytes 16 MB Largest result, in bytes.

ctx.formats.hex.decode(text)

  • Takes: hex text
  • Gives: a Uint8Array

Options: none.

Limits:

Limit Default What it limits
max_input_bytes 16 MB Largest input, in bytes.
max_output_bytes 16 MB Largest result, in bytes.

ctx.formats.xlsx.read(bytes, options?)

  • Takes: a Uint8Array with an .xlsx workbook
  • Gives: { sheet, sheets, epoch, formulas, rows }

Options:

Option Type Default What it does
sheet string | number the first sheet The sheet to read: its name (an exact match first, then ignoring case) or its 0-based number.
header boolean true Row 1 names the columns and every later row becomes an object. Otherwise every row is an array.
dates "iso" | "serial" “iso” Numbers with a date or time format become ISO 8601 text in the workbook’s date system, or stay serial numbers.

Limits:

Limit Default What it limits
max_input_bytes 16 MB Largest workbook file, in bytes.
max_entries 1,000 Most files inside the zip package.
max_uncompressed_bytes 128 MB Total unpacked size of the package, in bytes.
max_entry_bytes 64 MB Unpacked size of one file in the package, in bytes.
max_compression_ratio 200 Highest unpacked to packed size ratio of one file over 64 KiB.
max_sheets 256 Most sheets.
max_rows 200,000 Most rows, the header included.
max_columns 2,000 Most columns.
max_cells 2,000,000 Most cells in the rows read or written.
max_shared_strings 500,000 Most entries in the shared string table.
max_styles 64,000 Most cell formats, and most custom number formats.
max_cell_bytes 1 MB Longest text of one cell, in bytes.
max_output_bytes 32 MB Largest result: cell text for a read, the file for a write, in bytes.

ctx.formats.xlsx.write(rows, options?)

  • Takes: an array of rows, or an object of sheet name to rows
  • Gives: a Uint8Array with an .xlsx workbook

Options:

Option Type Default What it does
sheet string “Sheet1” The sheet name when the input is one array of rows.
header boolean true Rows are objects and the first row’s keys become a header row. Otherwise rows are arrays.

Limits:

Limit Default What it limits
max_input_bytes 16 MB Largest workbook file, in bytes.
max_entries 1,000 Most files inside the zip package.
max_uncompressed_bytes 128 MB Total unpacked size of the package, in bytes.
max_entry_bytes 64 MB Unpacked size of one file in the package, in bytes.
max_compression_ratio 200 Highest unpacked to packed size ratio of one file over 64 KiB.
max_sheets 256 Most sheets.
max_rows 200,000 Most rows, the header included.
max_columns 2,000 Most columns.
max_cells 2,000,000 Most cells in the rows read or written.
max_shared_strings 500,000 Most entries in the shared string table.
max_styles 64,000 Most cell formats, and most custom number formats.
max_cell_bytes 1 MB Longest text of one cell, in bytes.
max_output_bytes 32 MB Largest result: cell text for a read, the file for a write, in bytes.

ctx.formats.fixed.parse(data, options)

  • Takes: text or a Uint8Array
  • Gives: an array of objects with string values

Options:

Option Type Default What it does
fields { name, start?, width }[] required The layout. start is 1-based and defaults to right after the previous field; gaps are allowed, overlaps are refused.
unit "chars" | "bytes" required What start and width count: characters of the text, or bytes in the encoding.
encoding string “utf-8” The charset of bytes in, or of the bytes that unit: “bytes” counts. UTF-16 only works with unit: “chars”.
trim "both" | "left" | "right" | "none" “both” Which padding spaces to remove from every field. A field can set its own trim.
shortLines "error" | "pad" “error” A line shorter than the record is refused, or read as if padded with spaces.
longLines "error" | "ignore" “error” A line longer than the record is refused, or its extra characters are dropped on purpose.

Limits:

Limit Default What it limits
max_input_bytes 16 MB Largest input, in bytes.
max_rows 200,000 Most records (lines).
max_fields 1,000 Most fields in the layout.
max_line_length 65,536 Longest line and longest record, in the layout’s unit.
max_output_bytes 16 MB Largest result, in bytes.

ctx.formats.fixed.build(rows, options)

  • Takes: an array of objects
  • Gives: text (unit: “chars”) or a Uint8Array (unit: “bytes”)

Options:

Option Type Default What it does
fields { name, start?, width, align?, pad? }[] required The layout. align is “left” (the default) or “right”; pad is one character (a space by default).
unit "chars" | "bytes" required What start and width count: characters of the text, or bytes in the encoding.
encoding string “utf-8” unit: “bytes” only: the charset of the bytes written.
lineEnding "\n" | "\r\n" “\n” Written after every record.

Limits:

Limit Default What it limits
max_input_bytes 16 MB Largest input, in bytes.
max_rows 200,000 Most records (lines).
max_fields 1,000 Most fields in the layout.
max_line_length 65,536 Longest line and longest record, in the layout’s unit.
max_output_bytes 16 MB Largest result, in bytes.

What can make a data format operation fail. The error’s message names the problem and, when it can, the line.

Kind What went wrong
syntax The input is not well-formed.
input_too_large The input is larger than its limit.
output_too_large The result would be larger than its limit.
too_deep Values are nested deeper than the depth limit.
too_many_nodes The XML has more nodes than its limit.
too_many_attributes An element has more attributes than its limit.
doctype The XML has a DOCTYPE, which is never accepted.
entity The XML uses an entity other than the five predefined ones.
duplicate_attribute An element has the same attribute twice.
bom A byte order mark where none belongs.
encoding_mismatch The XML declaration names a different encoding than the text came in.
namespace A namespace prefix is not declared, or a declaration is invalid.
invalid_char A character XML does not allow.
invalid_name A name that is not a valid XML name.
invalid_value A value the builder cannot write.
invalid_option An unknown option, or an option with the wrong type or value.
too_many_rows More rows or lines than the limit.
too_many_columns A row has more fields than the limit.
ragged_row A row has a different number of fields than the header, or a fixed-width line has the wrong length.
line_too_long An NDJSON or fixed-width line is longer than its limit.
invalid_encoding The bytes are not valid in the named encoding.
unmappable A character has no representation in the target encoding.
invalid_encoded_text Base64 or hex text is malformed.
duplicate_field A form field repeats where only one is allowed, or a multipart part repeats a header.
too_many_fields More form fields, multipart parts or fixed-width fields than the limit.
resource_limit The data needed more memory, CPU or time than a built-in step allows.
archive The zip archive is damaged, unsupported, over a size or compression limit, or holds another archive.
macro The workbook has macros or is a binary or legacy Excel file, which is never read.
too_many_cells More cells, sheets, shared strings or cell styles than the limit.
field_too_long A value is longer than its field or cell allows.