Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.siftstack.com/llms.txt

Use this file to discover all available pages before exploring further.

After data is stored in Sift, it can be exported into MATLAB for analysis. Sift recommends using the official Python client via MATLAB’s built-in Python interface. If your team cannot use Python, the REST API is also supported natively from MATLAB.

Before you begin

To export data from Sift to MATLAB programmatically, make sure you have the following:

How exporting to MATLAB works

Sift provides two programmatic export mechanisms: data querying, which returns Channel data immediately in the response, and export data to file, which processes the export in the background and produces a downloadable file. Both mechanisms are available through the two paths that allow you to import data into MATLAB.

Path 1: MATLAB to Python bindings

Path 1 uses MATLAB’s built-in Python interface to call Sift’s official Python client (sift-stack-py) directly from your MATLAB environment. The py. prefix on any MATLAB command is what invokes that interface.

Set up the Python environment

Path 1 requires Python 3.8 or later installed on the same machine as MATLAB. Both mechanisms in Path 1 rely on the Sift Python client running inside a Python virtual environment that MATLAB can access. Complete the following steps once before running either mechanism. This sets up the Python environment, points MATLAB to it, and stores your Sift credentials.
1

Create a virtual environment

Run the following in your terminal:
python3 -m venv sift-matlab-env
source sift-matlab-env/bin/activate
2

Install the required packages

With the virtual environment active, install the Sift client and its dependencies:
pip install sift-stack-py pyarrow
3

Point MATLAB to your virtual environment

In the MATLAB Command Window, run:
pyenv(Version="/path/to/sift-matlab-env/bin/python")
Replace /path/to/sift-matlab-env with the full path to the virtual environment you created. To find it, run which python in your terminal while the virtual environment is active.You only need to do this setup once. MATLAB will remember the Python environment across sessions.
4

Verify your Python environment

Confirm MATLAB is pointing at the correct Python environment:
pyenv
If the wrong environment is selected, point MATLAB at the correct one:
pyenv(Version="/path/to/your/python")
5

Set your Sift credentials

Create a .env file in a text editor and save it to MATLAB’s current working directory. Run pwd in the MATLAB Command Window to find that location. Add the following to the file:
SIFT_API_KEY=your-api-key-here
SIFT_GRPC_URI=your-grpc-url-here
SIFT_REST_URI=your-rest-url-here
Then load the credentials in the MATLAB Command Window:
loadenv('.env')
disp(getenv('SIFT_API_KEY'))  % confirms the key was loaded

Mechanism 1: Data querying

1

Initialize the client

Run the following in the MATLAB Command Window to connect to Sift:
sift_mod = py.importlib.import_module('sift_client');
client = sift_mod.SiftClient(pyargs( ...
    'api_key',  getenv('SIFT_API_KEY'), ...
    'grpc_url', getenv('SIFT_GRPC_URI'), ...
    'rest_url', getenv('SIFT_REST_URI')));

disp(client)  % confirms the client connected
2

List Channels and query data

Use client.runs.find to find a single Run by name, or client.runs.list_ to search across multiple Runs. When a Run is provided to client.channels.get_data_as_arrow, the Run’s time range is used automatically so start_time and end_time are not needed.Find the Run. Use Option 1 to find a single Run by name:
run = client.runs.find(pyargs('name', 'YOUR_RUN_NAME'));
disp(run)  % shows the Run name, ID and time range
Use Option 2 to search across multiple Runs:
runs = client.runs.list_(pyargs('name', 'YOUR_RUN_NAME'));
run = runs{1};
disp(run)  % shows the Run name, ID and time range
Find Channels scoped to the Run using client.channels.list_:
channels = client.channels.list_(pyargs( ...
    'run',  run, ...
    'name', 'YOUR_CHANNEL_NAME'));

disp(channels)  % shows the Channel objects found
If you already know the Channel ID, you can use client.channels.get instead:
channel = client.channels.get(pyargs('channel_id', 'YOUR_CHANNEL_ID'));
channels = py.list({channel});
disp(channels)  % confirms the Channel was found
Query data for the full Run:
arrow_result = client.channels.get_data_as_arrow(pyargs( ...
    'channels', channels, ...
    'run',      run));

disp(arrow_result)  % shows the Python dict with Channel names and Arrow tables
To query a specific time window within the Run, pass start_time and end_time:
utc = py.datetime.timezone(py.datetime.timedelta(0));

start_dt = py.datetime.datetime(int32(YYYY), int32(MM), int32(DD), int32(HH), int32(MM), int32(SS), pyargs('tzinfo', utc));  % e.g. int32(2026), int32(4), int32(15), int32(0), int32(17), int32(49)
end_dt   = py.datetime.datetime(int32(YYYY), int32(MM), int32(DD), int32(HH), int32(MM), int32(SS), pyargs('tzinfo', utc));  % e.g. int32(2026), int32(4), int32(15), int32(0), int32(18), int32(26)

arrow_result = client.channels.get_data_as_arrow(pyargs( ...
    'channels',   channels, ...
    'run',        run, ...
    'start_time', start_dt, ...
    'end_time',   end_dt));

disp(arrow_result)  % shows the Python dict with Channel names and Arrow tables
arrow_result is a Python dict where each key is a Channel name and the value is an Apache Arrow table.
3

Write to Parquet and load into MATLAB

Data returned by get_data_as_arrow comes back as a Python object that MATLAB cannot consume directly. Writing it to a Parquet file and loading it with parquetread converts it into a format MATLAB understands.Replace YOUR_CHANNEL_NAME with the exact name of your Channel as it appears in Sift, for example temperature.
pq = py.importlib.import_module('pyarrow.parquet');
pq.write_table(arrow_result{'YOUR_CHANNEL_NAME'}, 'sift_export.parquet');

tt = parquetread('sift_export.parquet', ...
    'OutputType', 'timetable', ...
    'RowTimes',   'x__index_level_0__');

% Rename the time dimension to something readable
tt.Properties.DimensionNames{1} = 'time';

disp(tt)
The output will look similar to this:
            time            temperature
    ____________________    ___________

    15-Apr-2026 00:17:49      25.152
    15-Apr-2026 00:17:50      29.854
    15-Apr-2026 00:17:50      28.995
    15-Apr-2026 00:17:51      31.947
    15-Apr-2026 00:17:51      23.975
You will see a warning about table variable names being modified. This is expected. MATLAB automatically renames the internal timestamp column from __index_level_0__ to x__index_level_0__ to comply with MATLAB identifier rules. The timetable is created correctly and the warning can be safely ignored.

Mechanism 2: Export data to file

1

Submit the export job

Submit the job using client.data_export.export. The output_format parameter accepts ExportOutputFormat values including CSV, PARQUET, and SUN.
export_mod = py.importlib.import_module('sift_client.sift_types.export');
ExportOutputFormat = export_mod.ExportOutputFormat;

job = client.data_export.export(pyargs( ...
    'output_format', ExportOutputFormat.CSV, ...
    'runs',          py.list({'YOUR_RUN_ID'}), ...
    'channels',      py.list({'YOUR_CHANNEL_ID'})));

% Extract the job ID from the Python job object
jobId = char(job.id_);
disp(jobId)  % confirms the job was submitted
2

Wait for the job and download the result

Use client.jobs.wait_and_download with show_progress set to false to avoid the progress bar conflict with MATLAB’s Command Window:
client.jobs.wait_and_download(pyargs( ...
    'job',           job, ...
    'show_progress', false, ...
    'output_dir',    '.'));
3

Load the result into MATLAB

The exported file is saved to MATLAB’s current working directory. Run dir to find the filename:
dir('*.csv')
Then load it:
T = readtable('YOUR_FILENAME.csv', ...
    'Delimiter',          ',', ...
    'VariableNamingRule', 'preserve');
disp(T)
The column names include the full Run and Asset path, for example runName|assetName|temperature. To rename a column after loading:
T.Properties.VariableNames{2} = 'temperature';

Path 2: REST API via webread

Path 2 uses MATLAB’s native webread and webwrite functions to call the Sift REST API directly. No Python installation is required.

Mechanism 1: Data querying

1

Set your Sift credentials

If you already completed the Path 1 setup, your .env file already contains the required credentials. Run the following to load them:
loadenv('.env')
If you are starting with Path 2 directly, create a .env file in a text editor and save it to MATLAB’s current working directory. Run pwd in the MATLAB Command Window to find that location. Add the following to the file:
SIFT_API_KEY=your-api-key-here
SIFT_REST_URI=your-rest-url-here
Then load the credentials:
loadenv('.env')
2

Query Channel data

The examples below use webwrite to call POST /api/v2/data (GetData). See the GetData endpoint reference for the full schema.
opts = weboptions( ...
    'RequestMethod', 'post', ...
    'MediaType',     'application/json', ...
    'ContentType',   'json', ...
    'HeaderFields',  {'Authorization', ['Bearer ' getenv('SIFT_API_KEY')]});

query = struct( ...
    'queries', {{struct('channel', struct( ...
        'channelId', 'YOUR_CHANNEL_ID', ...
        'runId',     'YOUR_RUN_ID'))}}, ...
    'startTime', 'YYYY-MM-DDTHH:MM:SS.sssZ', ...  % e.g. 2026-04-15T00:17:49.984Z
    'endTime',   'YYYY-MM-DDTHH:MM:SS.sssZ', ...  % e.g. 2026-04-15T00:18:26.609Z
    'pageSize',  100000, ...
    'pageToken', '');

result = webwrite([getenv('SIFT_REST_URI') '/api/v2/data'], query, opts);
disp(result)  % shows the raw API response
If result.nextPageToken is non-empty, resend the request with pageToken set to that value to retrieve the next page.
3

Load into a MATLAB timetable

dt = struct2table(result.data(1).values);
dt.time = datetime(dt.timestamp, ...
    'InputFormat', 'uuuu-MM-dd''T''HH:mm:ss.SSS''Z''', ...
    'TimeZone',    'utc');
dt.timestamp = [];
tt = table2timetable(dt, 'RowTimes', 'time');

disp(tt)

Mechanism 2: Export data to file

1

Submit the export job

The examples below use webwrite to call POST /api/v1/export (ExportData) and GET /api/v1/export//download-url (GetDownloadUrl). See the ExportData endpoint reference and the GetDownloadUrl endpoint reference for the full schema.
opts = weboptions( ...
    'RequestMethod', 'post', ...
    'MediaType',     'application/json', ...
    'ContentType',   'json', ...
    'HeaderFields',  {'Authorization', ['Bearer ' getenv('SIFT_API_KEY')]});

exportBody = struct( ...
    'runsAndTimeRange', struct('runIds', {{'YOUR_RUN_ID'}}), ...
    'channelIds',       {{'YOUR_CHANNEL_ID'}}, ...
    'outputFormat',     'EXPORT_OUTPUT_FORMAT_CSV');

exportResult = webwrite([getenv('SIFT_REST_URI') '/api/v1/export'], exportBody, opts);
disp(exportResult)  % shows the job ID and status
2

Retrieve the download link

if ~isempty(exportResult.presignedUrl)
    websave('sift_export.zip', exportResult.presignedUrl);
else
    getOpts = weboptions( ...
        'HeaderFields', {'Authorization', ['Bearer ' getenv('SIFT_API_KEY')]}, ...
        'ContentType', 'json');
    jobId = exportResult.jobId;
    presignedUrl = '';
    while isempty(presignedUrl)
        pause(5);
        urlResult    = webread([getenv('SIFT_REST_URI') '/api/v1/export/' jobId '/download-url'], getOpts);
        presignedUrl = string(urlResult.presignedUrl);
    end
    websave('sift_export.zip', presignedUrl);
end
3

Unzip and load into MATLAB

unzip('sift_export.zip', 'sift_export');
The ZIP contains a file with a name generated by Sift. The format matches the outputFormat you specified when submitting the job, for example sift_data_export_2026-05-19_172841.csv for CSV. Run the following to see the exact filename:
dir('sift_export')
Then load it using the actual filename:
T = readtable('sift_export/YOUR_FILENAME.csv', ...
    'Delimiter',         ',', ...
    'VariableNamingRule', 'preserve');
disp(T)
The column names include the full run and asset path, for example runName|assetName|temperature. To rename a column after loading:
T.Properties.VariableNames{2} = 'temperature';

Verify

Once data is loaded into MATLAB, confirm it looks correct. Use tt for timetables (Mechanism 1) and T for tables (Mechanism 2).
  • Check the data is not empty:
    size(tt)   % for timetables
    size(T)    % for tables
    
  • Check the column names match your expected Channel names:
    tt.Properties.VariableNames   % for timetables
    T.Properties.VariableNames    % for tables
    
  • Check the timestamp range covers what you requested:
    tt.Properties.RowTimes([1 end])   % for timetables
    T{[1 end], 1}                     % for tables
    
  • Preview the first few rows:
    head(tt)   % for timetables
    head(T)    % for tables
    

Reference