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:

HeaderValue
Content-TypeThe file's MIME type, derived from its extension — application/vnd.openxmlformats-officedocument.spreadsheetml.sheet for an .xlsx.
Content-Dispositioninline, 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.

ContentEndpoint
A file on a FILE or CUSTOM sectionGET /api/v1/experiments/sections/{expJournalID}/files/{experimentFileID}
An image on an IMAGE, PARAGRAPH, or PROCEDURE sectionGET /api/v1/experiments/sections/{expJournalID}/images/{experimentFileID}
The drawing on a CANVAS sectionGET /api/v1/experiments/sections/{expJournalID}/canvas
The structure image on a MARVINJS sectionGET /api/v1/experiments/sections/{expJournalID}/marvinjs/image
The preview image of an Office Online sectionGET /api/v1/experiments/sections/{expJournalID}/oos/preview?oosExtension=xlsx
A completed experiment exportGET /api/v1/export/journal/download?exportID={exportID}
A file embedded in a protocolGET /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

SymptomCause / fix
404 with an empty bodyThe 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 interfaceThe 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 entryOffice Online sections report sectionType: "CUSTOM". Call customSectionInfo to find the ones with a custom sectionType of MSExcel.
400 from customSectionInfoThe 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 experimentThe 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 openThe 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 dateYou downloaded an older revision. Office Online sections keep every save; sort the file list on experimentFileID DESC and take the first record.

Did this page help you?