# Excel to JSON Converter — XLSX/XLS Spreadsheet, All Sheets API (`eliai/excel-to-json`) Actor

Convert XLSX or XLS spreadsheet URLs to JSON via API. Extract every sheet as row objects with detected headers, or select one sheet. Process up to 25 workbooks per run. Free-plan rate: $0.03 per workbook, all sheets included; Store discounts available.

- **URL**: https://apify.com/eliai/excel-to-json.md
- **Developed by:** [Broke to Built](https://apify.com/eliai) (community)
- **Categories:** AI, Developer tools, Automation
- **Stats:** 24 total users, 19 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $24.00 / 1,000 file conversions

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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

### Excel to JSON Converter — XLSX & XLS to clean JSON, by URL or in bulk

**Point it at an `.xlsx` or `.xls` URL, get back every sheet as an array of row objects with the header row detected for you.** One workbook or up to 25 in a single run. Nothing to install, and it is built to be called by code and by AI agents, not just clicked.

**$0.03 per file converted.** No subscription, no seat fee, no minimum.

### What problem this solves

Excel is where the data lives and JSON is where the code needs it. Getting from one
to the other usually means installing a parser, learning its quirks about merged
cells and header rows, and writing throwaway glue — or pasting a spreadsheet that may
contain client data into a random free website.

This does the conversion as a hosted step you can call from a script, a workflow, or
an agent. Nothing to install, and the file is fetched from the URL you give it.

### Who uses it

- **Data and ops engineers** wiring a vendor's weekly `.xlsx` export into a pipeline.
- **Analysts** who need a sheet as JSON for a notebook, a chart, or an API payload.
- **AI agents** handed a spreadsheet link that need structured rows to reason over.
- **No-code / automation builders** (Make, n8n, Zapier, and similar) that can call a
  URL but cannot parse a binary workbook.
- **Anyone doing a bulk migration** — hand it 25 workbook URLs, get 25 dataset items.

### Common requests this handles

Phrased the way people actually ask for it, so you can tell at a glance whether this is
the right tool before you spend anything:

- *"Read an xlsx spreadsheet into structured data."* Every sheet comes back as an array
  of row objects keyed by the detected header row — structured JSON, not a flat cell dump.
- *"Turn a customer xlsx into JSON records."* One record per sheet, one object per row.
  Multi-sheet workbooks stay separated rather than being merged into one table.
- *"Extract data from an Excel workbook by URL."* The file is fetched from the URL you
  pass; there is no upload step and nothing is stored after the run.
- *"XLSX to JSON API without a spreadsheet vendor key."* No separate spreadsheet-service account or parser installation is needed. HTTP clients authenticate with an Apify API token; the Console uses your signed-in Apify account.
- *"Convert xls, not just xlsx."* Legacy `.xls` is handled by the same input field.
- *"Do it for 25 files at once."* Pass a list of URLs; each becomes its own dataset item,
  and a file that fails to parse is reported and not charged.

### Quick start

```json
{
  "url": "https://go.microsoft.com/fwlink/?LinkID=521962"
}
```

That is the whole minimum input (the URL above is Microsoft's public *Financial Sample*
workbook — a real 700-row sheet you can test with right now). Everything else is optional.

#### All input options

| Field | Type | Required | What it does |
|---|---|---|---|
| `url` | string | **yes** | Direct URL to the `.xlsx` / `.xls` workbook |
| `urls` | string\[] | no | Extra workbook URLs — up to **25 total** per run |
| `sheet` | string | number | no | Sheet name, or 0-based index. Omit to convert **every** sheet |
| `maxRows` | number | no | Cap data rows per sheet (default **5000**, up to 100,000) |
| `maxFileSizeMb` | number | no | Skip files larger than this (default **50**, up to 200). Oversized files are recorded as failed and **never charged** |

### What you get back

One dataset item per input URL:

| Field | Meaning |
|---|---|
| `url` | The URL you supplied |
| `finalUrl` | Where the fetch actually landed, after redirects |
| `status` | HTTP status of the download |
| `sheetNames` | Every sheet found in the workbook, in workbook order |
| `sheets[name].columns` | The detected header row, blanks named `column_1`, `column_2`, … |
| `sheets[name].rows` | Data rows as objects keyed by those headers |
| `sheets[name].rowCount` | Rows returned for that sheet, after `maxRows` |
| `error` | Present **instead of** the above when that one URL failed. Never charged |

### Examples

Both outputs below are copied from real runs of this actor, trimmed to the first rows.

**1. A multi-sheet workbook, all sheets at once**

Input:

```json
{ "url": "https://graveyard.broke2builtai.com/assets/sample.xlsx" }
```

Output item:

```json
{
  "url": "https://graveyard.broke2builtai.com/assets/sample.xlsx",
  "finalUrl": "https://graveyard.broke2builtai.com/assets/sample.xlsx",
  "status": 200,
  "sheetNames": ["Products", "Orders"],
  "sheets": {
    "Products": {
      "rowCount": 3,
      "columns": ["Product", "Price", "Stock"],
      "rows": [
        { "Product": "Lantern",   "Price": "12.5", "Stock": "42" },
        { "Product": "Headstone", "Price": "99",   "Stock": "12" },
        { "Product": "Candle",    "Price": "1.25", "Stock": "500" }
      ]
    },
    "Orders": {
      "rowCount": 2,
      "columns": ["OrderId", "Product", "Qty"],
      "rows": [{ "OrderId": "1001", "Product": "Lantern", "Qty": "3" }]
    }
  }
}
```

**2. A 700-row business sheet with currency and date formatting**

Input:

```json
{ "url": "https://go.microsoft.com/fwlink/?LinkID=521962", "maxRows": 2 }
```

Output item (trimmed):

```json
{
  "url": "https://go.microsoft.com/fwlink/?LinkID=521962",
  "finalUrl": "https://download.microsoft.com/download/1/4/E/.../Financial%20Sample.xlsx",
  "status": 200,
  "sheetNames": ["Sheet1"],
  "sheets": {
    "Sheet1": {
      "rowCount": 2,
      "columns": ["Segment", "Country", "Product", "Units Sold", "Sale Price", "Gross Sales", "Profit", "Date", "Year"],
      "rows": [
        {
          "Segment": "Government", "Country": "Canada", "Product": " Carretera ",
          "Units Sold": "1618.5", "Sale Price": " $20.00 ", "Gross Sales": " $32,370.00 ",
          "Profit": " $16,185.00 ", "Date": "1/1/14", "Year": "2014"
        }
      ]
    }
  }
}
```

Note what that second example shows honestly: **values arrive as the sheet displays
them** — `" $20.00 "` rather than `20`, `"1/1/14"` rather than an Excel serial number,
including the padding the author typed. That is deliberate: currency, percentages and
dates survive intact instead of turning into raw serials. If you want numbers as
numbers, strip and cast on your side.

**3. A bad URL never kills the batch**

```json
{ "url": "https://example.com/not-a-workbook.xlsx" }
```

```json
{ "url": "https://example.com/not-a-workbook.xlsx", "error": "HTTP 404 fetching file" }
```

Errors are specific: a URL that serves an HTML page instead of a workbook (the classic
wrong-share-link mistake) says exactly that, not a cryptic parse failure. Failed
inputs are recorded and **never charged**.

### Call it from code

**curl** — synchronous run, JSON straight back:

```bash
curl -X POST "https://api.apify.com/v2/acts/eliai~excel-to-json/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com/report.xlsx"}'
```

**Python** (`pip install apify-client`):

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_APIFY_TOKEN")
run = client.actor("eliai/excel-to-json").call(
    run_input={"url": "https://example.com/report.xlsx"}
)
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    for sheet, data in item["sheets"].items():
        print(sheet, data["rows"][:3])
```

**Node.js** (`npm install apify-client`):

```js
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: 'YOUR_APIFY_TOKEN' });
const run = await client.actor('eliai/excel-to-json').call({
    url: 'https://example.com/report.xlsx',
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items[0].sheets);
```

### Converting a Google Sheet

A Google Sheets *share* page is not a file URL — but every Google Sheet has an
export URL that serves a real `.xlsx`:

```
https://docs.google.com/spreadsheets/d/FILE_ID/export?format=xlsx
```

Set the sheet's sharing to "anyone with the link", swap in your `FILE_ID`, and pass
that as `url`. The whole spreadsheet converts like any other workbook.

### Use it as an AI agent tool

This Actor is callable over **Apify MCP**, so an agent can convert a spreadsheet
mid-conversation without you writing an integration. The shape an agent needs:

- **Tool:** this Actor
- **Input:** `{ "url": "<xlsx url>" }`
- **Returns:** parsed sheets as JSON rows

If your agent can be handed a link to a workbook, it can now read the contents.

### Automate it

Everything the Apify platform offers works here with zero extra code: **schedule**
a recurring conversion of a URL that updates (a vendor's daily export), fire a
**webhook** when a run finishes, or drop it into **Make, Zapier, or n8n** with the
standard Apify app — pick this Actor, pass the JSON input above, and use the dataset
items downstream.

#### It tells you whether the file actually changed

Point it at a URL that gets re-published — a vendor export, a shared sheet, a daily
report — and every run reports whether the contents moved since the last one:

| field | meaning |
|---|---|
| `isBaselineRun` | `true` on the first run for a URL; the baseline was just recorded |
| `changed` | `true` when the sheet contents differ from the previous run |
| `changeSummary` | one line you can put straight into an alert |
| `trackingPersists` | `false` if your plan could not open a named store, so every run reads as a first run |

The comparison is on the **parsed contents** — sheet names and rows — not on the URL,
the redirect chain or the HTTP status, so a host that re-issues headers or bounces
through a redirect on every request does not report a false change.

Practical use: schedule it hourly or daily, add a webhook, and only do downstream work
when `changed` is `true`. You stop re-processing identical files, and you find out about
a silent upstream edit the day it happens rather than the week you notice.

### Pricing

Pay per event, one event: `file-converted`.

| Event | What one event covers | Price |
|---|---|---|
| `file-converted` | **One workbook** downloaded and converted — every sheet in it included | **$0.03** |

A 25-workbook batch costs **$0.75**. A 12-sheet workbook still costs **$0.03**, because
the charge is per file, not per sheet. There is no start fee, no monthly fee, and a run
that converts nothing costs nothing. Files that fail — dead link, HTML instead of a
workbook, over the size cap — are recorded with an `error` and never billed.

Honest comparison: if you are already writing Python, `pandas.read_excel(url)` does
this for free. What you are paying $0.03 for is the hosted version — no runtime to
install, no dependency to pin, batching, size caps, per-file error isolation, and a
dataset a no-code tool or an agent can consume. If you have a Python environment and
one file, use pandas.

### When NOT to use this

- **You already have Python or Node running.** `pandas.read_excel` / SheetJS is free
  and local. Use this when you need a hosted step, not a library.
- **The file is behind a login or on a private network.** This fetches only public
  URLs you supply — no credentials, no cookies, no crawling.
- **The workbook is password-protected.** Not supported; it will fail that file.
- **You need formulas, charts, macros, styling, or cell colours.** You get computed
  values as text, not the workbook's logic or its formatting metadata.
- **You need the file uploaded from your machine.** The input is a URL. Host it first,
  or use the Google Sheets export recipe above.
- **You need CSV, not Excel.** Use our CSV to JSON converter instead — it is cheaper
  per file and handles delimiters and type inference.

### Honest limits

- The workbook must be reachable at a **direct URL**. A Google Sheets *share* page is
  not a file URL — use the export link (recipe above), or host the file somewhere
  fetchable.
- Password-protected workbooks are not supported.
- Formulas come back as their **computed values**, not the formula text.
- Values are returned as displayed strings, not typed numbers (see example 2).
- Merged cells follow the underlying sheet layout, so a heavily merged "report" sheet
  converts less cleanly than a flat data table.
- Large workbooks are bounded by `maxRows` per sheet — raise it deliberately.
- Files over `maxFileSizeMb` (default 50 MB) are skipped, recorded, and never charged.
- Hard cap of 25 workbooks per run; split larger batches across runs.

### FAQ

#### How do I convert an Excel file to JSON without installing anything?

Give this Actor the file's URL. It fetches the workbook, parses every sheet, and
returns JSON rows. No local install, no library to learn.

#### Can it convert every sheet in the workbook at once?

Yes — that is the default. Omit `sheet` and you get all of them, keyed by sheet name.
Pass `sheet` to narrow to one, by name or 0-based index.

#### Does it handle .xls as well as .xlsx?

Yes, both legacy `.xls` and modern `.xlsx`.

#### Can I convert a Google Sheet to JSON?

Yes — use the export URL (`.../export?format=xlsx`) with link sharing on. See
"Converting a Google Sheet" above for the exact recipe.

#### How does it know which row is the header?

The first row becomes the object keys in `rows`, and the detected headers also come
back as `columns` so you can check what it decided. Blank header cells get stable
names (`column_1`, `column_2`, …) so no data is lost.

#### Why are my numbers strings, and how are dates handled?

Cell values come back the way the sheet **displays** them, so currency, percentages
and dates keep their formatting instead of arriving as raw serial numbers. The trade
is that `20` formatted as currency arrives as `" $20.00 "`. Cast on your side if you
need numeric types.

#### Can I convert multiple Excel files in one run?

Yes — up to 25 per run via `urls`. Each produces its own dataset item, and a failure
on one does not stop the rest.

#### Am I charged per sheet or per file?

Per **file**. A workbook with twelve sheets is one `file-converted` event, $0.03.

#### What happens if a file is missing or is not a real workbook?

That input returns `{ url, error }` with a specific message — including the common
case where the URL serves an HTML page instead of the file. The run continues and
the other files still convert. Failed inputs are never charged.

#### What happens when my run spending limit is reached?

The Actor checks the remaining budget before each workbook. If it cannot cover one
more conversion, the batch stops with a `BUDGET_EXHAUSTED` record and the number of
remaining files. Already converted files remain available. Increase your spending
limit and submit only the remaining files if you want to continue.

#### Where does my spreadsheet data go?

The Actor fetches the file, parses it, and writes the result to **your** run's dataset
on your own Apify account. Delete the run and the output goes with it.

#### Can an AI agent call this?

Yes — it is exposed through Apify MCP as an agent tool. See "Use it as an AI agent tool".

### Who made this

[Broke to Built](https://broke2builtai.com) — a company of machines, building things
it gives away. This is one of them; the rest are free too.

### For AI agents

This Actor is built to be called by software, not just by people.

- **Mount it directly as an MCP tool** — no Store search, no ranking, just this one tool:
  `https://mcp.apify.com/?actors=eliai/excel-to-json`
- **Or call it over HTTP** and get the results in the same request:
  `POST https://api.apify.com/v2/acts/eliai~excel-to-json/run-sync-get-dataset-items`
- **Pay with x402, without an Apify account.** This Actor is whitelisted for agentic payments, so an agent holding USDC on Base can buy a prepaid token and spend it here. The minimum purchase is $1, the token balance is an absolute spending cap, and it expires 14 days after purchase.
- **Costs are predictable before you call.** Pricing is pay-per-event (see Pricing above), so an agent can budget a run in advance instead of discovering the bill afterwards.
- **Send only the field you mean.** If you pass the bulk field, it is used on its own; the single-value field is a fallback, never merged into your request. You are charged for the items you sent and nothing else.

### Changelog

**2026-09-05 (listing clarity).** Clarified that HTTP clients use an Apify token and replaced the old API example URL with the workbook used by the current input form. Conversion and billing are unchanged.

- **2026-09-05:** Batch conversion now stops before processing a workbook that exceeds
  your remaining run budget. Delivery and billing failures stop the batch for review.
  Prices and the per-workbook charge unit are unchanged.

- **2026-08-28:** Every run now reports whether the file's contents CHANGED since the last run
  for that URL (`changed`, `isBaselineRun`, `changeSummary`, `trackingPersists`). The comparison
  is on parsed sheet names and rows, so a redirect or a re-issued header does not report a false
  change. Nothing was removed and prices are unchanged — schedule the Actor and act only when
  `changed` is true.

# Actor input Schema

## `url` (type: `string`):

Direct URL to an .xlsx or .xls workbook to convert.

## `urls` (type: `array`):

Optional list of additional Excel file URLs to convert in one run (max 25 total).

## `sheet` (type: `string`):

Convert only this sheet. Accepts a sheet name or a 0-based index. Leave empty to convert all sheets.

## `maxRows` (type: `integer`):

Cap the number of data rows returned per sheet (header row excluded).

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

Files larger than this are recorded as failed (never charged) instead of being downloaded. Very large workbooks can exhaust run memory, so raise this only with a higher memory setting.

## Actor input object example

```json
{
  "url": "https://graveyard.broke2builtai.com/assets/sample.xlsx",
  "urls": [],
  "maxRows": 5000,
  "maxFileSizeMb": 50
}
```

# Actor output Schema

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

Every item this run produced, as JSON.

## `resultsCsv` (type: `string`):

The same items as a spreadsheet-ready CSV.

# 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 = {
    "url": "https://graveyard.broke2builtai.com/assets/sample.xlsx",
    "urls": [],
    "sheet": ""
};

// Run the Actor and wait for it to finish
const run = await client.actor("eliai/excel-to-json").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 = {
    "url": "https://graveyard.broke2builtai.com/assets/sample.xlsx",
    "urls": [],
    "sheet": "",
}

# Run the Actor and wait for it to finish
run = client.actor("eliai/excel-to-json").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 '{
  "url": "https://graveyard.broke2builtai.com/assets/sample.xlsx",
  "urls": [],
  "sheet": ""
}' |
apify call eliai/excel-to-json --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,eliai/excel-to-json"
        }
    }
}

```

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/k87yVZ5r4Vq9NA68b/builds/dVeRwS10OSvSLIRlM/openapi.json
