XLSB to JSON API
Send an Excel binary workbook to POST /v2/convert/xlsb/to/json and get one sheet as a JSON array, an object per row keyed by the header row. The sample's Readings sheet gives 25 objects: 24 hourly readings and the Total row.
- Endpoint
- POST /v2/
convert/ xlsb/ to/ json - Accepts
- xlsb
- Sample timing
- under 1 ms median of 5 runsHow we measure
- Request size
- Up to 10 MB on the Free plan or 100 MB on paid plans, all files together
Request and response
Every sample below ran against the API before this page was published. Set PDFCONVERSIONAPI_KEY to your key and run it as is.
curl -sS -X POST \
https://api.pdfconversionapi.com/v2/convert/xlsb/to/json \
-H "Authorization: Bearer $PDFCONVERSIONAPI_KEY" \
-F File=@meter-readings.xlsb \
-o response.json
# FileData is base64. Save the first file in converted/,
# or print the JSON error.
if jq -e '.Files' response.json > /dev/null; then
mkdir -p converted
name=$(jq -r '.Files[0].FileName' response.json)
jq -r '.Files[0].FileData' response.json \
| base64 --decode > "converted/$name"
else
cat response.json
fiimport base64
import os
import requests
key = os.environ["PDFCONVERSIONAPI_KEY"]
response = requests.post(
"https://api.pdfconversionapi.com/v2/convert/xlsb/to/json",
headers={"Authorization": f"Bearer {key}"},
files=[("File", open("meter-readings.xlsb", "rb"))],
timeout=180,
)
result = response.json()
if not response.ok:
raise SystemExit(f"{result['Code']}: {result['Message']}")
os.makedirs("converted", exist_ok=True)
for file in result["Files"]:
path = os.path.join("converted", file["FileName"])
with open(path, "wb") as out:
out.write(base64.b64decode(file["FileData"]))
print(path, file["FileSize"], "bytes")
import { mkdir, readFile, writeFile } from "node:fs/promises";
import { join } from "node:path";
const form = new FormData();
const file = "meter-readings.xlsb";
form.append("File", new Blob([await readFile(file)]), file);
const url = "https://api.pdfconversionapi.com/v2/convert/xlsb/to/json";
const key = process.env.PDFCONVERSIONAPI_KEY;
const response = await fetch(url, {
method: "POST",
headers: { Authorization: `Bearer ${key}` },
body: form,
});
const result = await response.json();
if (!response.ok) {
throw new Error(`${result.Code}: ${result.Message}`);
}
await mkdir("converted", { recursive: true });
for (const file of result.Files) {
const path = join("converted", file.FileName);
await writeFile(path, Buffer.from(file.FileData, "base64"));
console.log(path, file.FileSize, "bytes");
}
{
"ConversionCost": 1,
"ConversionTime": 0,
"Files": [
{
"FileName": "meter-readings.json",
"FileExt": "json",
"FileSize": 2472,
"FileData": "WwogIHsKICAgICJNZXRlciI6…"
}
]
}Response from our test run on 2026-11-03. ConversionTime shows the median of 5 runs. FileData is shortened here; the real response carries the whole file as base64.
The sample, converted
The same file the code sends. Download the input and the output and compare them yourself.
meter-readings.json, first 30 of 152 lines
[ { "Meter": "CS-1", "Peak": false, "Time": "2026-03-02", "kWh": 3.18 }, { "Meter": "CS-1", "Peak": false, "Time": "2026-03-02 01:00:00", "kWh": 3.05 }, { "Meter": "CS-1", "Peak": false, "Time": "2026-03-02 02:00:00", "kWh": 2.97 }, { "Meter": "CS-1", "Peak": false, "Time": "2026-03-02 03:00:00", "kWh": 2.94 }, { "Meter": "CS-1", "Peak": false, "Time": "2026-03-02 04:00:00", "kWh": 2.99
- Input
- meter-readings.xlsb (3.3 KB)
- Output
- meter-readings.json (2.4 KB)
- Source
- A fictional cold-storage unit's meter readings in Excel binary format (two sheets with dates, times and formulas) that we wrote record by record for this site (CC0-1.0). How it was made
- Made by
- 2026-11-04, API build
9eeeb0640aba
Parameters
Send these as form fields next to the file. Names are case-insensitive. If you set one of the documented options below that we don't support yet, the request fails with a 400 that names it instead of returning a file made without it.
| Parameter | What it does |
|---|---|
WorksheetNamestring | Sheet to read, matched in any letter case: daily returns the 8 rows of the Daily sheet (602 bytes). Without it the first sheet is read. |
WorksheetIndexinteger | Sheet by position from 1; 3 returns 400 "WorksheetIndex is 3, but the workbook has 2 worksheets" for the sample.Values: 1 to the number of sheets |
ActiveWorksheetOnlyboolean, default false | Read the tab that was open when the workbook was saved, as its workbook view records it. The sample was saved on Daily, so true returns the same 602 bytes as WorksheetName=daily. A WorksheetName or WorksheetIndex sent with it wins. |
FirstRowIsHeaderboolean, default true | Set false to get arrays in column order instead of objects, with the header row as the first array.Values: true, false |
Passwordstring | Opens an XLSB encrypted with a password to open; our encrypted copy of the sample gave the same 2,472 bytes with it and 422 without it. |
FileNamestring | Name for the JSON file. |
What carries over, and what doesn't
Kept
- Timestamps as ISO text: "2026-03-02 01:00:00" for an hour reading, and "2026-03-02" alone for the midnight reading
- Decimal values such as 3.18 and 101.29, and TRUE/FALSE cells as JSON booleans
- The stored result of a formula: the Total row's kWh is 101.29, from =SUM(C2:C25)
- Every row of the used range, including the Total row, whose empty cells are null
Lost or not yet supported
- Display rounding: the Daily sheet's Average shows 96.86 in Excel but arrives as the stored 96.861429.
- Column order inside objects: keys are sorted alphabetically (Meter, Peak, Time, kWh).
- Formulas, formatting, the second sheet and anything that isn't a cell value.
Limits
- One sheet per request. There is no
xlsb/to/mdroute (404); CSV is available at/v2/convert/xlsb/to/csv. - A file that isn't a spreadsheet returns 422 "the input file could not be read: Cannot detect file format".
- Requests up to 10 MB on the Free plan or 100 MB on paid plans; 120 seconds per conversion, then 504.
Where teams use it
Getting data out of large XLSB exports
Some reporting tools and analysts deliver tables as XLSB. Reading them as JSON lets services that have no XLSB reader use the data.
Loading meter, sensor or transaction logs
Timestamped rows arrive as ISO text with numbers and flags typed, ready to insert into a time-series store.
Questions
Why does the midnight reading have no time?
Date cells whose time is exactly 00:00:00 are written as the date alone ("2026-03-02"); any other time adds it ("2026-03-02 01:00:00"). Parse both forms, or treat a missing time as midnight.
Do I need Excel to read XLSB on the server?
No. The binary records are read directly by our converter, which doesn't run Excel or any office suite for this route.
Which sheet is converted?
The first one, unless you name another with WorksheetName or WorksheetIndex. The sample was saved with Daily open, yet the JSON comes from Readings, the first sheet, unless you send ActiveWorksheetOnly=true.
Convert your first XLSB file in a minute.