SafeXL Excel Spreadsheet Editor for AI Agents avatar

SafeXL Excel Spreadsheet Editor for AI Agents

Pricing

Pay per event

Go to Apify Store
SafeXL Excel Spreadsheet Editor for AI Agents

SafeXL Excel Spreadsheet Editor for AI Agents

Edits Excel .xlsx and .xlsm spreadsheets for AI agents with no code sandbox. The receipt checks that macros, charts and pivots are still there. Also verifies a file against its original, rescues one a model re-saved, and shows a compact view.

Pricing

Pay per event

Rating

0.0

(0)

Developer

D. Michael Piscitelli

D. Michael Piscitelli

Maintained by Community

Actor stats

1

Bookmarked

2

Total users

1

Monthly active users

3 days ago

Last modified

Share

Edits Excel .xlsx and .xlsm spreadsheets for AI agents with no code sandbox. The receipt checks that macros, charts and pivots are still there. Also verifies a file against its original, rescues one a model re-saved, and shows a compact view.

What it does

Edits .xlsx and .xlsm files for agents that can't run code. Send the file and the changes, get the file back with a receipt.

Why it exists: a model that edits a workbook by writing code re-saves the whole file through a library. Parts it never touched can go missing. Macros gone, charts orphaned.

Doesn't: delete rows, insert columns, make charts, recalculate. Refuses worksheets written with a namespace prefix (some files from the .NET OpenXML SDK). Tested in LibreOffice, not Excel.

The Actor does no AI work: it applies your operations directly to the file, then checks its own result against your original. A file is delivered only when that check passes. It does four jobs. edit applies an ordered list of operations and delivers the new file. verify compares an edited file with its original and returns the receipt. salvage takes a copy that another tool re-saved and lost features, re-applies its cell edits to the original and delivers the rebuilt file. view returns a compact text view of a sheet, so you do not have to read raw XML.

What the receipt covers

Covers: loss of a tracked feature (macros, charts, pivots, validation, defined names and the rest), and untouched parts byte-identical to your original.

Doesn't cover: whether the cell values or formulas are the ones you meant. Check the cells you changed. The receipt lists defined names and table ranges that changed, under reference_changes. It can't tell a wanted change from an unwanted one. That list covers names and tables only. A data validation that grew or moved down, or a chart series whose rows changed, is not listed.

verify runs the same check on any two files you send. It finds lost features and parts that changed when they shouldn't. It is not a tamper check on cell values.

Formulas are stored as written. The Actor does not evaluate or sanitise them, including WEBSERVICE, DDE and HYPERLINK formulas. Whether they run is up to the application that opens the file. Check formulas from an untrusted source before you send them.

Risky additions don't block an edit. Add a WEBSERVICE formula or hide a sheet and you still get the file, and pay for it. You asked for it. The receipt names each one under added_risk.

Also in the receipt: summary, one sentence. cell_changes, counts per sheet. reference_changes, each defined name or table range that differs.

Operations

Each ops entry: one JSON object written as text, with an op field. Cells are single cells such as B2. A bad entry is refused with its index and reason. Nothing is written or charged.

opFieldsExample 1Example 2
set_valuesheet, cell, value (number, true/false or text){"op": "set_value", "sheet": "Sales", "cell": "B2", "value": 120}{"op": "set_value", "sheet": "Sales", "cell": "A7", "value": "East"}
set_formulasheet, cell, formula{"op": "set_formula", "sheet": "Sales", "cell": "E2", "formula": "=B2*C2"}{"op": "set_formula", "sheet": "Summary", "cell": "B3", "formula": "=AVERAGE(Sales!C2:C6)"}
set_textsheet, cell, text (never turned into a number){"op": "set_text", "sheet": "Items", "cell": "A5", "text": "007"}{"op": "set_text", "sheet": "Sales", "cell": "G1", "text": "Reviewed"}
insert_rowssheet, row, count (default 1){"op": "insert_rows", "sheet": "Sales", "row": 3}{"op": "insert_rows", "sheet": "Sales", "row": 2, "count": 4}
insert_cellssheet, range, shift (down){"op": "insert_cells", "sheet": "Sales", "range": "B5:C6", "shift": "down"}{"op": "insert_cells", "sheet": "Items", "range": "D2:D2"}
rename_sheetsheet, new_name{"op": "rename_sheet", "sheet": "Summary", "new_name": "Totals"}{"op": "rename_sheet", "sheet": "Sheet1", "new_name": "Q3 Data"}
add_sheetname{"op": "add_sheet", "name": "Notes"}{"op": "add_sheet", "name": "Checks"}
resize_tabletable, ref, sheet (optional){"op": "resize_table", "table": "SalesTbl", "ref": "A1:E8"}{"op": "resize_table", "table": "ItemsTbl", "ref": "A1:D10", "sheet": "Items"}

Formulas: stored as written, calculated when Excel or LibreOffice next opens the file. The Actor doesn't calculate them. Files come from an https URL to a public file, a record key in a key-value store you pick in the input, or sample://sales and sample://inventory (two small workbooks to try it on).

For AI agents

In: one JSON object. Out: one dataset row per entry. Every row has the same fields:

  • input_index: position of the entry in your input list, from 0. Rows come back in input order.
  • outcome: ok, or one of four that are never charged: empty (nothing found), out_of_scope (refused), error, skipped (a limit was reached).
  • units_charged: events this entry cost you.
  • result: the typed result for ok rows, null otherwise.
  • error: {type, message, retryable}, or null. type is a fixed list of codes. retryable: whether the same entry may succeed on a later run.

Example input:

{
"jobs": [
{
"operation": "view",
"workbook": "sample://sales",
"sheet": "Sales",
"max_cells": 40
},
{
"operation": "edit",
"workbook": "sample://sales",
"ops": [
"{\"op\": \"set_value\", \"sheet\": \"Sales\", \"cell\": \"B2\", \"value\": 120}",
"{\"op\": \"set_formula\", \"sheet\": \"Sales\", \"cell\": \"E2\", \"formula\": \"=B2*C2\"}"
]
}
]
}

One real row, from the second job of the example above (an edit), build 0.1.11. The store id and the record key are masked:

{
"input_index": 1,
"source": "edit",
"outcome": "ok",
"units_charged": 1,
"result": {
"operation": "edit",
"engine": "safexl 0.2.0",
"input_sha256": "56769aa760e66abc37afda7be543bf040206bfeaf85248e11c72af58a9c02d38",
"receipt": {
"verdict": "intact",
"valid": true,
"lost_features": [],
"rebuilt": false,
"parts": {
"unchanged": 14,
"changed": [
"xl/worksheets/sheet1.xml"
],
"added": [],
"removed": []
},
"before_sha256": "56769aa760e66abc37afda7be543bf040206bfeaf85248e11c72af58a9c02d38",
"after_sha256": "298a37672377766ef9a726a7d5ee5193a8376fa198d0a356f461e510f35b6605",
"reasons": [],
"added_risk": [],
"cell_changes": {
"Sales": {
"values_changed": 2,
"formulas_changed": 0,
"styles_changed": 0,
"added": 0,
"removed": 0,
"rows_removed": 0,
"cells_changed": 2,
"complete": true
}
},
"summary": "No tracked feature was lost and untouched parts are byte-identical; 2 cells changed on Sales; nothing risky was added."
},
"output_sha256": "298a37672377766ef9a726a7d5ee5193a8376fa198d0a356f461e510f35b6605",
"output_bytes": 7857,
"macro_enabled": false,
"ops_applied": 2,
"output": {
"key": "<record key of the edited file>",
"store_id": "<store id>",
"url": "https://api.apify.com/v2/key-value-stores/<store id>/records/job-1-edit.xlsx"
}
},
"notices": [],
"error": null,
"cost_usd": 0,
"latency_s": 0.823
}

Edited file: the record named in result.output.key, in the run's default key-value store. Its sha256 is result.output_sha256. receipt.verdict is intact whenever a file is delivered.

out_of_scope: the Actor refused the job. error.type says why (for example invalid_ops, edit_refused, not_intact, unsafe_workbook, url_not_allowed, record_not_found). A refusal is never charged.

Input

  • jobs: Each job is one operation on one workbook: edit, verify, salvage or view. Up to 5 entries per run, each up to 400000 characters. Each entry is a JSON object with these fields:
    • operation (string, required): Operation. edit applies the ops and delivers the edited file with a receipt. verify compares workbook against original and returns a receipt. salvage re-applies the cell edits found in a damaged copy (workbook) to the original and delivers the rebuilt file. view returns a compact text view of the workbook.
    • workbook (string, required): Workbook. The file to act on: an https URL to a public .xlsx or .xlsm, a record key in the key-value store picked in 'Key-value store with your workbooks', or sample://sales or sample://inventory for a small bundled workbook. For verify this is the edited file; for salvage it is the damaged copy.
    • original (string, optional): Original workbook. Required for verify and salvage: the workbook before the edit, in the same forms as 'workbook'.
    • ops (array, optional): Operations (edit). Required for edit. An ordered list, 1 to 40, each a JSON object written as text with an "op" field: set_value {sheet, cell, value}, set_formula {sheet, cell, formula}, set_text {sheet, cell, text}, insert_rows {sheet, row, count}, insert_cells {sheet, range, shift: down}, rename_sheet {sheet, new_name}, add_sheet {name}, resize_table {table, ref, sheet}.
    • extend_tables (boolean, optional): Extend tables (edit). When true (the default), a cell written directly beside an Excel table grows the table to include it, as Excel does.
    • sheet (string, optional): Sheet (view). view only: the sheet to show. The first sheet when empty; all sheets when 'find' is set and this is empty.
    • range (string, optional): Range (view). view only: an A1 cell or range such as A1:F40 to limit the view.
    • find (string, optional): Find (view). view only: list cells whose value or formula contains this text, case-insensitive.
    • max_cells (integer, optional): Max cells (view). view only: the most cells to return, 1 to 2000. The result says when it was cut off and where to continue.

Output

One dataset row per entry, in input order. The Overview tab shows the same rows as a table.

Pricing

You pay per delivered result, and only for results the Actor delivered:

  • $0.05 per edited workbook (workbook-edit): the edited file, with a receipt whose verdict is intact.
  • $0.10 per salvaged workbook (workbook-salvage): the rebuilt file, with an intact receipt. A salvage can be partial: the edits it could not carry over are listed in result.salvage.skipped, and it is still charged. Check that list.
  • $0.01 per receipt (workbook-verify).
  • $0.002 per view (workbook-view).

Apify's standard start fee applies once per run: $0.00005.

Refused jobs, errors, timeouts and skipped jobs are never charged. The start fee applies to every run, even one where every job is refused. Runs on the free Apify plan handle up to 3 results per run.

Limits

  • Entries: up to 5 per run, each up to 400000 characters.
  • Time: 60 s per entry. A longer one is stopped, returned as a timeout error, not charged. A job usually takes a few seconds.
  • Files: .xlsx and .xlsm up to 20 MB. Refused: password-protected files, legacy .xls, files with an XML DOCTYPE. The Actor never opens or runs a macro. A macro project is copied byte for byte.
  • URLs: https only, public hosts only, at most 3 redirects, 20 seconds.
  • Key-value records: loaded whole before the size check. A record far over 20 MB can use up the 1 GB of memory and fail the run. Keep workbooks under 20 MB.
  • Size: row limits (rows stored in the file, including a row that holds only formatting), checked before the work starts where the Actor can read the sheet list. Cell edits (set value, formula, text): 300,000 rows in the sheet edited. Row and cell inserts: 100,000 rows in that sheet. verify: 300,000 rows across all sheets of each of the two files. Over a limit: refused as file_too_complex, not charged (the start fee still applies). Each limit is the largest size that finished in every run on Apify at 1 GB on 4 October 2026 (build 0.1.11), sheets with one number per row, four runs each. 100,000 rows: set values 4 to 6 s, insert 1 row 12 to 24 s, verify 9 to 12 s. 300,000 rows: set values 10 to 15 s, verify 23 to 27 s. On build 0.1.14, single runs of set values at 300,000 rows, insert at 100,000 and verify at 300,000 fell inside or under those ranges. Measured on set value and on inserting one row; formula, text and cell inserts use the same limits unmeasured. Several inserts in one entry take longer than one. A denser workbook is slower. An edit re-checks the whole file, so other large sheets in the workbook add time. view, salvage, rename, add sheet and resize table are not row-checked. Of these, only view was measured on a large sheet (one run, 700,000 rows, about 10 s). The others can still time out on a very large workbook.
  • Too slow: stopped, returned as timeout, not charged. Too big for memory: refused as file_too_complex, not charged. Retrying a file_too_complex refusal doesn't help.
  • Damaged inside (a corrupt part, a bad checksum): refused as not_a_workbook. A symbolic-link entry: refused as unsafe_workbook.
  • Operations: at most 40 per edit. No formula calculation, no clearing cells, no deleting rows, no chart or pivot edits.

Contact

Issues tab on the Actor's page: wrong results and questions. I read it and reply as I can.