SFTP in Calculations in TAS 5.7

A guide for consultants who need to send a file to an SFTP server, or download a file from one, from a task calculation in TAS 5.7. Version 5.7 has no separate ftp API. File transfer is done with the curl object (node-libcurl library). The axios object supports only HTTP and HTTPS. The procedure was verified on a TAS 5.7.105 instance. Reading was tested against an SFTP server (listing, downloading text and binary files, error states, password from a secret). Writing (upload, rename, creating and deleting a directory, downloading back) was tested against an FTP server as described in chapter 9. The upload and QUOTE command mechanism is the same for both protocols; only the command names differ.

The default curl settings in 5.7 support SFTP (SSH, port 22). For classic FTP (port 21), you must also set the PROTOCOLS_STR option in the calculation, otherwise curl.perform() fails with the error Unsupported protocol. The procedure is in chapter 9.

What 5.7 can and cannot do

Requirement

TAS 5.7

How

HTTP/HTTPS REST calls

yes

axios (recommended) or curl

SFTP – upload, download, listing, deleting

yes

curl

Classic FTP (port 21)

yes

curl with the PROTOCOLS_STR option, see chapter 9

Separate ftp API (ftp.getFtpClient(...))

no

from version 5.12.22, see FTP a SFTP použití ve výpočtech (in Czech)

SmartEvent "FTP requester"

no

only in newer versions

Behaviour of the curl object in 5.7

  • The curl object is available only in task calculations (Task JS) and in identity calculations. It is not available in case calculations or in the calculations console.
  • The default protocols are HTTP, HTTPS and SFTP. FTP is enabled with the PROTOCOLS_STR option (chapter 9).
  • After the first curl.perform(), a warning that CurlApi is deprecated is written to the backend log. It is only a warning; the functionality works normally.
  • The pre-upgrade template check (system.validateTemplate(tprocId)) reports every use of curl.* with the text "removed, use FtpApi". Ignore this in 5.7; it is preparation for the upgrade.
  • curl.perform() closes the connection when it finishes. For another request in the same calculation, call curl.start() again, otherwise it fails with the error Curl handle is closed.
  • The result of perform() is an object { statusCode, data, headers }. For SFTP, statusCode is always 0; errors are raised as exceptions.
  • The default timeout is 60 s. For larger files, set curl.setOpt('TIMEOUT', 600).

1. Verifying that the server has SFTP in libcurl

Run this in a test calculation on a task (the curl object is not available in the calculations console):

vars['vysledek'].setValue(curl.info());

The output must include sftp in the list of protocols and libssh2 (or libssh) among the libraries. The standard 5.7 image has libcurl/7.86.0 … libssh2/1.10.0 and scp, sftp in the protocols. If they are missing, the image has no SSH support and this must be resolved at the installation level.

2. Credentials

Three options, from the simplest to the most secure.

a) In the URL

Suitable only for testing; the password is in the template text.

curl.setOpt('URL', 'sftp://uzivatel:heslo@sftp.example.com:22/export/soubor.txt');

Special characters in the password must be URL-encoded (@ → %40, : → %3A, / → %2F). It is easier to use encodeURIComponent(heslo).

b) With the USERPWD option

The URL then contains no credentials and no encoding is needed.

curl.setOpt('URL', 'sftp://sftp.example.com:22/export/soubor.txt');
curl.setOpt('USERPWD', 'uzivatel:heslo');

c) From a secret

The password is not in the template code. The secret can have any name and its value is the string uzivatel:heslo. It is set either in local.js under the secrets key, or once via a calculation (the value is stored in the instance configuration; then remove the calculation from the template):

await lib.setSecret('sftpExport', 'uzivatel:heslo');

In the calculation, instead of curl.perform(), call curl.performSecureAuth(nazevSecretu), which loads the secret, sets USERPWD and performs the request:

curl.setOpt('URL', 'sftp://sftp.example.com:22/export/soubor.txt');
const response = await curl.performSecureAuth('sftpExport');
Always use option c) in production. A password in the URL or in USERPWD is visible to anyone who is allowed to edit the template.

Key instead of password

libcurl supports the SSH_PRIVATE_KEYFILE and SSH_PUBLIC_KEYFILE options (path to a file on the TAS server, typically in a mapped volume). This option is not covered by platform tests; it works at the libcurl level.

3. Downloading a file from SFTP

A text file; the response arrives as a string:

curl.start();
curl.setOpt('URL', 'sftp://sftp.example.com:22/export/data.csv');
curl.setOpt('USERPWD', 'uzivatel:heslo');
curl.setOpt('TIMEOUT', 300);

const { data } = await curl.perform();
vars['csvObsah'].setValue(data);

A binary file (PDF, XLSX, ZIP) saved to the case DMS. Without curl.enable('Raw'), the content is corrupted by conversion to a string (verified: a 19,156 B PNG arrived as a string of 18,136 characters):

curl.start();
curl.setOpt('URL', 'sftp://sftp.example.com:22/export/faktura.pdf');
curl.setOpt('USERPWD', 'uzivatel:heslo');
curl.setOpt('TIMEOUT', 300);
curl.enable('Raw');

const { data } = await curl.perform(); // data je Buffer
const dmsfId = await lib.storeAttachment('faktura.pdf', data);

lib.storeAttachment(nazev, Buffer) saves the file as an attachment of the current case and returns its DMS ID. If you want to assign it to an attachment variable, set the variable to the returned ID.

4. Uploading a file to SFTP

Content created in the calculation, for example CSV:

const csv = 'id;nazev\n1;Test\n';
const datum = new Date().toISOString().slice(0, 10); // 2026-11-09

curl.start();
curl.setOpt('URL', 'sftp://sftp.example.com:22/import/export_' + datum + '.csv');
curl.setOpt('USERPWD', 'uzivatel:heslo');
curl.setOpt('TIMEOUT', 300);
curl.setOpt('UPLOAD', true);
curl.sendBinaryBuffer(Buffer.from(csv, 'utf-8'));

await curl.perform();

An attachment from the DMS (attachment variable, file name or DMS ID):

const obsah = await storage.getDmsContent(vars['priloha'], 'buffer');   // Buffer

curl.start();
curl.setOpt('URL', 'sftp://sftp.example.com:22/import/' + encodeURIComponent('smlouva.pdf'));
curl.setOpt('USERPWD', 'uzivatel:heslo');
curl.setOpt('TIMEOUT', 600);
curl.setOpt('UPLOAD', true);
curl.sendBinaryBuffer(obsah);

await curl.perform();
The order matters: UPLOAD and sendBinaryBuffer before perform. The target directory must exist on the server; SFTP does not create it during upload (see chapter 6). Without write permission, perform() fails with the error Access denied to remote resource. Upload via UPLOAD + sendBinaryBuffer and downloading back were verified against an FTP server (35 B including diacritics, identical content).

5. Directory listing

The URL must end with a slash. The default output is a long format like ls -l; the DIRLISTONLY option returns only names, one per line:

curl.start();
curl.setOpt('URL', 'sftp://sftp.example.com:22/export/');
curl.setOpt('USERPWD', 'uzivatel:heslo');
curl.setOpt('DIRLISTONLY', true);

const { data } = await curl.perform();
const soubory = data.split('\n').filter(n => n && n !== '.' && n !== '..');
return soubory;

Without DIRLISTONLY, the listing arrives in the form -rw------- 1 demo users 379 Sep 19 2023 readme.txt, one entry per line, including . and ...

6. Deleting, renaming, creating a directory

libcurl can run commands on SFTP via the QUOTE option (before the transfer) or POSTQUOTE (after the transfer). Supported commands: rm, rmdir, rename, mkdir, chmod, ln, symlink. The commands are sent immediately after login, before changing to the directory from the URL, so use absolute paths in them; a relative path is resolved against the account root. If a command fails on the server (missing permissions, non-existent path), perform() throws Quote command returned error.

Deleting a file after a successful download (the "process and clean up" pattern):

curl.start();
curl.setOpt('URL', 'sftp://sftp.example.com:22/export/data.csv');
curl.setOpt('USERPWD', 'uzivatel:heslo');
curl.setOpt('POSTQUOTE', ['rm /export/data.csv']);

const { data } = await curl.perform();

Moving to an archive after upload:

curl.setOpt('POSTQUOTE', ['rename /import/data.csv /import/archiv/data.csv']);

Creating a directory before upload:

curl.setOpt('QUOTE', ['mkdir /import/2026-11']);

Commands only, without a transfer, for example to delete a file without downloading anything:

curl.start();
curl.setOpt('URL', 'sftp://sftp.example.com:22/export/');
curl.setOpt('USERPWD', 'uzivatel:heslo');
curl.setOpt('NOBODY', true);
curl.setOpt('QUOTE', ['rm /export/stary.csv']);
await curl.perform();

7. Error handling

On error, curl.perform() throws an exception with the libcurl error text, for example Couldn't connect to server, Login denied, Timeout was reached, Unsupported protocol, Access denied to remote resource (write without permission) or Quote command returned error. Recommended pattern:

try {
curl.start();
curl.setOpt('URL', 'sftp://sftp.example.com:22/export/data.csv');
curl.setOpt('USERPWD', 'uzivatel:heslo');
curl.setOpt('TIMEOUT', 120);
const { data } = await curl.perform();
vars['csvObsah'].setValue(data);
} catch (err) {
throw new Error('SFTP stažení selhalo: ' + err.message);
}

The error is then shown to the user in the task and written to the calculations log.

8. Common problems

Symptom

Cause and solution

Unsupported protocol

The URL starts with ftp:// or ftps://. 5.7 supports only sftp://.

Login denied

Wrong password or unencoded special characters in the URL. Use USERPWD.

The downloaded PDF is corrupted

curl.enable('Raw') is missing; the content was converted to a string.

The second request in the same calculation fails

Error Curl handle is closed. or Cannot add an Easy handle that is closed.: curl.start() is missing before the next request; perform() closes the connection.

Access denied to remote resource

The account has no write permission for the target path, or the target directory does not exist.

Upload of a large file ends with a timeout

The default limit is 60 s; set TIMEOUT.

Upload to a non-existent directory fails

SFTP does not create directories; use QUOTE with mkdir.

9. Classic FTP

For ftp:// URLs in 5.7, you must set the PROTOCOLS_STR option with a list of protocols after curl.start(). Without it, perform() fails with the error Unsupported protocol. Verified on 5.7.105: listing, upload, downloading back, MKD, renaming, DELE and RMD.

Classic FTP sends both the password and the data unencrypted; use it only within an internal network. After upgrading to a version with the ftp API, rewrite this code according to chapter 10.

Directory listing and upload differ from SFTP only in the protocol in the URL and one extra line:

curl.start();
curl.setOpt('PROTOCOLS_STR', 'ftp,sftp,http,https');
curl.setOpt('URL', 'ftp://ftp.example.com/import/' + encodeURIComponent('data.csv'));
curl.setOpt('USERPWD', 'uzivatel:heslo');
curl.setOpt('TIMEOUT', 300);
curl.setOpt('UPLOAD', true);
curl.sendBinaryBuffer(Buffer.from(csv, 'utf-8'));

const r = await curl.perform(); // r.statusCode je FTP kód odpovědi, např. 226

For FTP, the QUOTE commands are classic FTP commands, again with absolute paths:

Operation

SFTP

FTP

create a directory

mkdir /import/archiv

MKD /import/archiv

rename / move

rename /import/a.csv /import/archiv/a.csv

RNFR /import/a.csv immediately followed by RNTO /import/archiv/a.csv

delete a file

rm /import/archiv/a.csv

DELE /import/archiv/a.csv

delete a directory

rmdir /import/archiv

RMD /import/archiv

curl.start();
curl.setOpt('PROTOCOLS_STR', 'ftp,sftp,http,https');
curl.setOpt('URL', 'ftp://ftp.example.com/import/');
curl.setOpt('USERPWD', 'uzivatel:heslo');
curl.setOpt('NOBODY', true);
curl.setOpt('QUOTE', ['MKD /import/archiv', 'RNFR /import/data.csv', 'RNTO /import/archiv/data.csv']);
await curl.perform();

10. What changes after the upgrade

From version 5.12.22, the ftp API is available, which also supports classic FTP, and curl is removed in newer versions. The template check reports this before the upgrade. When migrating from 5.7, simply replace the curl.* blocks with calls to the corresponding client method:

const client = ftp.getFtpClient({
protocol: 'sftp', // nebo 'ftp'
host: 'sftp.example.com',
port: 22,
username: 'uzivatel',
password: 'heslo',
});

await client.list('/export');
await client.upload(vars['priloha'], '/import/smlouva.pdf'); // zdroj je DMS soubor
await client.download('/export/data.csv'); // vrátí lokální cestu
await client.exists('/export/data.csv');
await client.delete('/export/data.csv');
await client.rename('/import/a.csv', '/import/archiv/a.csv');
await client.createDirectory('/import/2026-11', true);
await client.removeDirectory('/import/old', true);

From version 5.17, the password can be passed from the Vault via ftp.getFtpClientWithVaultPassword(config, vault.get('nazev')). A complete description of the new API, including allowed paths, is in the article FTP a SFTP použití ve výpočtech (in Czech).

Contact

Syca (opens in a new tab)

Powered by HelpDocs (opens in a new tab)