API file download
How to download files using the eLabAPI
File download using the eLabAPI
The eLabAPI serves files as raw binary data. This page explains how to find the file you want inside an experiment, download it, and write it to disk without corrupting it. You need an API token — see Receiving an API key — and the base URL of your eLabNext environment. For the other direction, see API file upload.
Download responses are raw binary
Every download endpoint returns the bytes of the file as the response body. There is no JSON envelope and no base64 wrapper, so parsing the response as JSON fails, and reading it as text corrupts the file. Read the raw bytes and write them in binary mode:
import requests
url = f"https://{base_url}/api/v1/experiments/sections/{expJournalID}/files/{experimentFileID}"
headers = {
"X-Requested-With": "Swagger",
"Authorization": token,
}
response = requests.get(url, headers=headers)
response.raise_for_status()
# response.content holds the bytes; "wb" writes them unchanged
with open("section-file.xlsx", "wb") as file:
file.write(response.content)Two response headers describe what you received:
| Header | Value |
|---|---|
Content-Type | The file's MIME type, derived from its extension — application/vnd.openxmlformats-officedocument.spreadsheetml.sheet for an .xlsx. |
Content-Disposition | inline, with filename set to the file's original name in eLabNext. Read it if you want to save the file under that name. |
The disposition is inline rather than attachment, so a browser opening the URL directly may render the file instead of saving it. In a script this makes no difference — you write the bytes yourself.
Find the section that holds your file
Files live on experiment sections, and every download call takes the section's expJournalID — not the experiment ID. List the sections of an experiment to find it:
GET /api/v1/experiments/{experimentID}/sections
Each entry in data carries an expJournalID and a sectionType:
{
"expJournalID": 5289713,
"sectionType": "CUSTOM",
"header": "Out-of-specification results"
}Office Online sections report a sectionType of CUSTOM
An Excel, Word, or PowerPoint section created through the Office Online add-on is stored as a custom section. Its sectionType is CUSTOM, not EXCEL, so filtering the section list for EXCEL will not find it. To tell which kind of custom section you are looking at, ask for its custom section information:
GET /api/v1/experiments/sections/{expJournalID}/customSectionInfo
[
{
"rootVar": "OFFICEONLINE",
"version": "1.0.0",
"name": "MS Excel",
"sectionType": "MSExcel"
}
]The sectionType field here is the custom section type. Office Online sections use MSExcel, MSWord, and MSPowerPoint. This endpoint returns 400 if the section is not a CUSTOM section at all, which is a quick way to rule a section out.
List the files in a section
Both FILE and CUSTOM sections expose their files through the same endpoint:
GET /api/v1/experiments/sections/{expJournalID}/files
The response is a paginated list of file metadata:
{
"totalRecords": 3,
"data": [
{
"experimentFileID": 8842301,
"realName": "OOS results.xlsx",
"fileSize": 18342,
"stored": "2026-02-10T09:14:22Z",
"origin": "S3",
"parentExperimentFileID": 8842288
}
]
}Sections edited in Office Online keep every revision
Each save in the Office Online editor adds a new file to the section rather than replacing the existing one, and parentExperimentFileID links each revision to the one before it. A section that has been edited a few times therefore returns several files, all with the same realName.
The current revision is the one with the highest experimentFileID. Sort on that field and take the first record:
GET /api/v1/experiments/sections/{expJournalID}/files?$sort=experimentFileID DESC&$records=1
See Expand and Sort for the query parameter syntax.
Download the file
With the section and the file identified, request the file itself:
GET /api/v1/experiments/sections/{expJournalID}/files/{experimentFileID}
Putting the three calls together — find the newest file in a section and save it under its original name:
import requests
base_url = "your_base_url" # e.g. sandbox.elabjournal.com
token = "your_api_token" # API token from Apps & Connections
expJournalID = "your_section_id" # the section, not the experiment
headers = {
"X-Requested-With": "Swagger",
"Authorization": token,
}
# 1. Take the most recent file in the section
list_url = f"https://{base_url}/api/v1/experiments/sections/{expJournalID}/files"
params = {"$sort": "experimentFileID DESC", "$records": 1}
response = requests.get(list_url, headers=headers, params=params)
response.raise_for_status()
files = response.json().get("data", [])
if not files:
raise SystemExit(f"Section {expJournalID} has no files")
latest = files[0]
file_id = latest["experimentFileID"]
file_name = latest["realName"]
# 2. Download it and write the bytes to disk
file_url = f"https://{base_url}/api/v1/experiments/sections/{expJournalID}/files/{file_id}"
response = requests.get(file_url, headers=headers)
response.raise_for_status()
with open(file_name, "wb") as file:
file.write(response.content)
print(f"Saved {file_name} ({len(response.content)} bytes)")For large files, stream the response instead of holding it in memory:
with requests.get(file_url, headers=headers, stream=True) as response:
response.raise_for_status()
with open(file_name, "wb") as file:
for chunk in response.iter_content(chunk_size=8192):
file.write(chunk)The following recipe is the complete script in both JavaScript and Python. It identifies the section, downloads the current revision, and ends with a reference for handling each file type once it is saved.
Files stored on eLABHybrid
If an institute runs eLABHybrid, file content can live on the institute's own storage instead of in eLabNext. Those files come back from the file list with "origin": "ONSITE" and an extra hybridStorage object:
{
"experimentFileID": 8842301,
"realName": "raw-instrument-data.csv",
"origin": "ONSITE",
"hybridStorage": {
"url": "https://storage.institute.example/elabhybrid",
"localStorageRequest": "<encrypted request parameter>"
}
}Requesting an ONSITE file through the section files endpoint returns 404. Send the localStorageRequest value to the url from the same response to retrieve the content from the institute's eLABHybrid server.
Other downloadable content
The same binary-response rules apply to every endpoint below.
| Content | Endpoint |
|---|---|
A file on a FILE or CUSTOM section | GET /api/v1/experiments/sections/{expJournalID}/files/{experimentFileID} |
An image on an IMAGE, PARAGRAPH, or PROCEDURE section | GET /api/v1/experiments/sections/{expJournalID}/images/{experimentFileID} |
The drawing on a CANVAS section | GET /api/v1/experiments/sections/{expJournalID}/canvas |
The structure image on a MARVINJS section | GET /api/v1/experiments/sections/{expJournalID}/marvinjs/image |
| The preview image of an Office Online section | GET /api/v1/experiments/sections/{expJournalID}/oos/preview?oosExtension=xlsx |
| A completed experiment export | GET /api/v1/export/journal/download?exportID={exportID} |
| A file embedded in a protocol | GET /api/v1/protocols/file/{fileID} |
The image, canvas, and protocol file endpoints accept an optional maxWidth query parameter to receive a width-constrained version of the image with its aspect ratio preserved.
Troubleshooting
| Symptom | Cause / fix |
|---|---|
| 404 with an empty body | The ID in the path is not a section ID. Every section endpoint takes the section's expJournalID, not the experiment ID or the file ID. |
| 404 on a section you can see in the interface | The section holds no files yet, or the file is stored on eLABHybrid ("origin": "ONSITE"). Check the file list first. |
The section list has no EXCEL entry | Office Online sections report sectionType: "CUSTOM". Call customSectionInfo to find the ones with a custom sectionType of MSExcel. |
400 from customSectionInfo | The section is not a CUSTOM section. The message reads The specified experiment section is not of type 'CUSTOM'. |
403 You don't have view permission for this experiment | The token's user cannot view the experiment. Check the experiment's collaboration settings, and that the token has the read scope. |
| The downloaded file will not open | The response body was decoded as text. Write response.content in binary mode ("wb"), never response.text. |
| The spreadsheet opens but the contents are out of date | You downloaded an older revision. Office Online sections keep every save; sort the file list on experimentFileID DESC and take the first record. |
Updated about 4 hours ago