Using the TofuPilot MATLAB SDK

Last updated on May 21, 2026

The TofuPilot MATLAB SDK is a type-safe toolbox that wraps the TofuPilot REST API. Requests are plain MATLAB structs and responses are decoded structs. Source on GitHub at tofupilot/matlab, MIT licensed.

Installation

Clone the repo and add it to your MATLAB path. Requires MATLAB R2019b or later, no extra toolboxes.

terminal
git clone https://github.com/tofupilot/matlab.git
setup.m
addpath('/path/to/matlab');
savepath;

Authentication

Pass your API key as the first argument to the constructor:

run.m
sdk = tofupilot.TofuPilot('<YOUR_API_KEY_HERE>');

Or read the env var:

terminal
export TOFUPILOT_API_KEY="your-api-key"
quickstart.m
sdk = tofupilot.TofuPilot(getenv('TOFUPILOT_API_KEY'));

req.procedure_id = 'your-procedure-id';
req.serial_number = 'SN001';
req.part_number = 'PN001';
req.outcome = 'PASS';

run = sdk.Runs.create(req);
fprintf('Run created: %s\n', run.id);

Examples

Each method maps to a REST endpoint. The REST API reference sidebar lists all endpoints with MATLAB snippets. Common workflows below.

Create a run with measurements

Nest a phase with measurements under phases. Use cell-of-struct ({{...}}) wrapping so MATLAB encodes the JSON array correctly.

create_run.m
now = char(datetime('now', 'TimeZone', 'UTC', 'Format', 'yyyy-MM-dd''T''HH:mm:ss''Z'''));

measurement = struct( ...
    'name', 'output_voltage', ...
    'outcome', 'PASS', ...
    'measured_value', 3.3, ...
    'unit', 'V');

phase = struct( ...
    'name', 'voltage_check', ...
    'outcome', 'PASS', ...
    'started_at', now, ...
    'ended_at', now, ...
    'measurements', {{measurement}});

req.procedure_id = 'your-procedure-id';
req.serial_number = 'SN-001';
req.part_number = 'PCB-V1';
req.outcome = 'PASS';
req.started_at = now;
req.ended_at = now;
req.phases = {{phase}};

run = sdk.Runs.create(req);

List and filter runs

Pass filters as name-value pairs to list. Pagination is cursor-based: result.meta.has_more and result.meta.next_cursor.

list_runs.m
cursor = [];
while true
    if isempty(cursor)
        result = sdk.Runs.list('partNumbers', {'PCB-V1'}, 'outcomes', {'PASS'}, 'limit', 50);
    else
        result = sdk.Runs.list('partNumbers', {'PCB-V1'}, 'outcomes', {'PASS'}, 'limit', 50, 'cursor', cursor);
    end

    for i = 1:numel(result.data)
        run = result.data(i);
        fprintf('%s - %s\n', run.id, run.unit.serial_number);
    end

    if ~result.meta.has_more
        break;
    end
    cursor = result.meta.next_cursor;
end

Upload and download attachments

Static helpers on tofupilot.AttachmentHelpers wrap the three-step REST flow (initialize, PUT to pre-signed URL, finalize) into one call.

MethodSignatureReturns
attachToRunattachToRun(runs, runId, filePath)char (attachment ID)
attachToUnitattachToUnit(units, serialNumber, filePath)char (attachment ID)
downloaddownload(downloadUrl, destinationPath)char (path)
attachments.m
attachmentId = tofupilot.AttachmentHelpers.attachToRun(sdk.Runs, run.id, 'report.pdf');

run = sdk.Runs.get(run.id);
tofupilot.AttachmentHelpers.download(run.attachments(1).download_url, 'report-copy.pdf');

unitAttachmentId = tofupilot.AttachmentHelpers.attachToUnit(sdk.Units, 'SN-0001', 'calibration.pdf');
sdk.Units.deleteAttachment('SN-0001', 'ids', {unitAttachmentId});

Helpers throw MException with identifiers tofupilot:FileNotFound, tofupilot:InvalidUrl, or tofupilot:UploadFailed on failure.

Error handling

HTTP failures rethrow the underlying MException. Match on identifier or status from the response body.

errors.m
try
    sdk.Runs.get('nonexistent-id');
catch err
    fprintf('Error: %s\n', err.message);
    if contains(err.identifier, 'NotFound')
        fprintf('Run does not exist.\n');
    end
end

Retries

The client retries 429 and 5xx responses with exponential backoff (3 retries at 1s, 2s, 4s by default). Override via MaxRetries.

retries.m
sdk = tofupilot.TofuPilot('your-api-key', 'MaxRetries', 5);

% Disable retries
sdk = tofupilot.TofuPilot('your-api-key', 'MaxRetries', 0);

Hooks

Attach lifecycle hooks to log or instrument requests, successful responses, and errors.

hooks.m
sdk = tofupilot.TofuPilot('your-api-key', ...
    'BeforeRequest', {@(ctx) fprintf('-> %s %s\n', ctx.method, ctx.path)}, ...
    'AfterSuccess', {@(ctx, resp) fprintf('OK\n')}, ...
    'AfterError', {@(ctx, err) fprintf('Error: %s\n', err.message)});

Configuration

All configuration is passed as name-value pairs on the constructor; per-call overrides are not supported.

config.m
sdk = tofupilot.TofuPilot('your-api-key', ...
    'BaseUrl', 'https://your-server.com/api', ...
    'Timeout', 60, ...
    'MaxRetries', 5);

Self-hosted

To target a self-hosted instance, pass BaseUrl as the instance origin plus /api (the SDK appends /v2/... per call).

self_hosted.m
sdk = tofupilot.TofuPilot('your-api-key', ...
    'BaseUrl', 'https://your-instance.example.com/api');

How is this guide?

On this page