Backend Connections
Section titled “Backend Connections”The Files service exposes endpoints for connecting to remote storage backends over FTP, S3, and SSH/SFTP, and for managing the authentication session. All endpoints share a container-scoped base URL of the form https://{projectId}-{containerId}-files-1.{server}.containers.hoody.com, and remote credentials are supplied as query parameters in the request URL.
Authentication
Section titled “Authentication”CHECKAUTH /{path}
Section titled “CHECKAUTH /{path}”Verify the current authentication status. The response body is the authenticated username as plain text, or empty if the request is unauthenticated.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
path | path | string | Yes | The path to check authentication for |
Response
Section titled “Response”The authenticated username as a plain text string. The body is empty when the request is not authenticated.
aliceThe request path is not valid.
{ "success": false, "error": "Invalid path", "code": "INVALID_PATH"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INVALID_PATH | Invalid path | The request path is not valid: it does not decode, it has a . or .. component, it is outside the configured path prefix, or it is over the filesystem’s limits (a name over 255 bytes, a path over 4095 bytes) | Send a path of plain components under the served directory, within the filesystem’s limits |
The request is refused for this path.
{ "success": false, "error": "Access forbidden", "code": "ACCESS_FORBIDDEN"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ACCESS_FORBIDDEN | Access forbidden | The request is refused: the account may not access this path or where it leads (a symlink to a path the account may not access), the deployment has not enabled this operation (uploads, deletes, touches, permission and owner changes, renames, extraction, thumbnails), or the path is one this server does not write or follow (the served directory itself, a symlink when symlinks are not allowed, a read-only or immutable file, a pipe, a socket or a device, which has no content to read or copy) | Use an account with access to the path, or ask the deployment’s administrator to enable the operation; a pipe, a socket or a device is never read |
Example
Section titled “Example”curl --request CHECKAUTH \ "https://{projectId}-{containerId}-files-1.{server}.containers.hoody.com/"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-files-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.files.whoami('/');LOGOUT /{path}
Section titled “LOGOUT /{path}”Clear the authentication session. The response sets a WWW-Authenticate header to force the client to drop any cached credentials.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
path | path | string | Yes | The path to clear authentication for |
Response
Section titled “Response”The request path is not valid.
{ "success": false, "error": "Invalid path", "code": "INVALID_PATH"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INVALID_PATH | Invalid path | The request path is not valid: it does not decode, it has a . or .. component, it is outside the configured path prefix, or it is over the filesystem’s limits (a name over 255 bytes, a path over 4095 bytes) | Send a path of plain components under the served directory, within the filesystem’s limits |
Authentication has been cleared. The response sets a WWW-Authenticate header so the client drops any cached credentials.
The request is refused for this path.
{ "success": false, "error": "Access forbidden", "code": "ACCESS_FORBIDDEN"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ACCESS_FORBIDDEN | Access forbidden | The request is refused: the account may not access this path or where it leads (a symlink to a path the account may not access), the deployment has not enabled this operation (uploads, deletes, touches, permission and owner changes, renames, extraction, thumbnails), or the path is one this server does not write or follow (the served directory itself, a symlink when symlinks are not allowed, a read-only or immutable file, a pipe, a socket or a device, which has no content to read or copy) | Use an account with access to the path, or ask the deployment’s administrator to enable the operation; a pipe, a socket or a device is never read |
Example
Section titled “Example”curl --request LOGOUT \ "https://{projectId}-{containerId}-files-1.{server}.containers.hoody.com/"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-files-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.files.logout('/');Remote SSH/SFTP
Section titled “Remote SSH/SFTP”GET /{path}?type=ssh
Section titled “GET /{path}?type=ssh”Open an SFTP session to a remote SSH server and read a file or list a directory. The response body is the raw file content (application/octet-stream) for a file, or a directory listing for a directory. Authenticate with a password, a private key, or both.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
path | path | string | Yes | The file or directory path on the remote SSH server |
type | query | string | Yes | Backend type. Must be ssh |
server | query | string | Yes | Server hostname and port, for example sftp.example.com:22 |
user | query | string | Yes | SSH username |
pass | query | string | No | Password (base64 encoded) |
key | query | string | No | Private key in PEM form (base64 encoded) |
passphrase | query | string | No | Key passphrase (base64 encoded) |
Response
Section titled “Response”The raw file content as a binary stream (application/octet-stream) for a file path, or a directory listing for a directory path. The wire format is not JSON.
The request has an invalid path, parameter, header, or body.
{ "success": false, "error": "Invalid path", "code": "INVALID_PATH"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INVALID_PARAMETER | Invalid request | A query parameter, a header or the request body is not one this operation accepts, or a copy or a move names a destination inside the directory it copies or moves; the message names what was refused | Correct the request |
INVALID_PATH | Invalid path | The request path is not valid: it does not decode, it has a . or .. component, it is outside the configured path prefix, or it is over the filesystem’s limits (a name over 255 bytes, a path over 4095 bytes) | Send a path of plain components under the served directory, within the filesystem’s limits |
The request is refused for this path.
{ "success": false, "error": "Access forbidden", "code": "ACCESS_FORBIDDEN"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ACCESS_FORBIDDEN | Access forbidden | The request is refused: the account may not access this path or where it leads (a symlink to a path the account may not access), the deployment has not enabled this operation (uploads, deletes, touches, permission and owner changes, renames, extraction, thumbnails), or the path is one this server does not write or follow (the served directory itself, a symlink when symlinks are not allowed, a read-only or immutable file, a pipe, a socket or a device, which has no content to read or copy) | Use an account with access to the path, or ask the deployment’s administrator to enable the operation; a pipe, a socket or a device is never read |
Nothing is at this path.
{ "success": false, "error": "Not found", "code": "RESOURCE_NOT_FOUND"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
RESOURCE_NOT_FOUND | Not found | Nothing is at this path, or the path is private to the server and answers as if absent | Check the path |
Pending uploads from before a service restart are still being processed on this backend. A Retry-After header indicates the seconds to wait before retrying.
{ "success": false, "error": "Uploads from before a service restart are still pending on this backend; retry later."}The service cannot carry out this request right now. A Retry-After header indicates the seconds to wait before retrying.
{ "success": false, "error": "Mounts are unavailable. Retry after the time given in Retry-After.", "code": "MOUNT_STORAGE_UNAVAILABLE"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
MOUNT_STORAGE_UNAVAILABLE | Mounts unavailable | Mounts are unavailable until the server’s storage check passes; the mount directory and the directory of the backends’ own mounts may overlap, so no mount or backend connection can start, and backend file operations are refused before the backend is looked up | Retry after the time given in Retry-After |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
BACKEND_UNREACHABLE | Remote backend unreachable | A read of a remote (through ?backend=, or one-off through ?type=) could not reach it: the remote did not answer within 30 seconds (504), or it refused the connection, its TLS or the credentials (502). Nothing was read. | Check the connection details and credentials (for a saved backend, GET /api/v1/backends/{id}/test, and PUT /api/v1/backends/{id} to update them), or retry once the remote is reachable |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
BACKEND_UNREACHABLE | Remote backend unreachable | A read of a remote (through ?backend=, or one-off through ?type=) could not reach it: the remote did not answer within 30 seconds (504), or it refused the connection, its TLS or the credentials (502). Nothing was read. | Check the connection details and credentials (for a saved backend, GET /api/v1/backends/{id}/test, and PUT /api/v1/backends/{id} to update them), or retry once the remote is reachable |
Example
Section titled “Example”curl -G \ --data-urlencode "type=ssh" \ --data-urlencode "server=sftp.example.com:22" \ --data-urlencode "user=alice" \ --data-urlencode "pass=c2VjcmV0" \ "https://{projectId}-{containerId}-files-1.{server}.containers.hoody.com/path/to/file.txt"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-files-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.files.ssh.get('/path/to/file.txt', { type: 'ssh', server: 'sftp.example.com:22', user: 'alice', pass: 'c2VjcmV0',});PUT /{path}?type=ssh
Section titled “PUT /{path}?type=ssh”Open an SFTP session to a remote SSH server and write a file. The request body is the file content as a raw binary stream (application/octet-stream). Authenticate with a password, a private key, or both.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
path | path | string | Yes | The file path to write to on the remote SSH server |
server | query | string | Yes | Server hostname and port, for example sftp.example.com:22 |
user | query | string | Yes | SSH username |
pass | query | string | No | Password (base64 encoded) |
key | query | string | No | Private key in PEM form (base64 encoded) |
passphrase | query | string | No | Key passphrase (base64 encoded) |
Request body
Section titled “Request body”The file content as a raw binary stream, sent with Content-Type: application/octet-stream.
Response
Section titled “Response”The file was uploaded to the remote SSH server.
The request has an invalid path, parameter, header, or body. This includes an upload that broke off before it ended, where nothing was sent to the remote backend.
{ "success": false, "error": "Upload incomplete", "code": "UPLOAD_INCOMPLETE"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INVALID_PARAMETER | Invalid request | A query parameter, a header or the request body is not one this operation accepts, or a copy or a move names a destination inside the directory it copies or moves; the message names what was refused | Correct the request |
INVALID_PATH | Invalid path | The request path is not valid: it does not decode, it has a . or .. component, it is outside the configured path prefix, or it is over the filesystem’s limits (a name over 255 bytes, a path over 4095 bytes) | Send a path of plain components under the served directory, within the filesystem’s limits |
UPLOAD_INCOMPLETE | Upload incomplete | The upload’s body broke off before it ended, so nothing was sent to the remote backend | Retry the upload |
The request is refused for this path.
{ "success": false, "error": "Access forbidden", "code": "ACCESS_FORBIDDEN"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ACCESS_FORBIDDEN | Access forbidden | The request is refused: the account may not access this path or where it leads (a symlink to a path the account may not access), the deployment has not enabled this operation (uploads, deletes, touches, permission and owner changes, renames, extraction, thumbnails), or the path is one this server does not write or follow (the served directory itself, a symlink when symlinks are not allowed, a read-only or immutable file, a pipe, a socket or a device, which has no content to read or copy) | Use an account with access to the path, or ask the deployment’s administrator to enable the operation; a pipe, a socket or a device is never read |
Pending uploads from before a service restart are still being processed on this backend. A Retry-After header indicates the seconds to wait before retrying.
{ "success": false, "error": "Uploads from before a service restart are still pending on this backend; retry later."}The upload body exceeds the configured maximum upload size.
{ "success": false, "error": "Upload exceeds the configured max upload size", "code": "PAYLOAD_TOO_LARGE"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
PAYLOAD_TOO_LARGE | Body too large | The body is over the limit of this operation: the deployment’s upload limit, or 64 KiB for the JSON body of PATCH | Send a smaller body |
The remote SSH backend refused or failed the upload. The body arrived whole and within the size limit.
{ "success": false, "error": "The remote backend failed the upload", "code": "REMOTE_UPLOAD_FAILED"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
REMOTE_UPLOAD_FAILED | Remote upload failed | The remote backend refused or failed the upload; the message carries its error. The body arrived whole and within the limit. | Check the backend and retry |
The service cannot carry out this request right now. A Retry-After header indicates the seconds to wait before retrying.
{ "success": false, "error": "Mounts are unavailable. Retry after the time given in Retry-After.", "code": "MOUNT_STORAGE_UNAVAILABLE"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
MOUNT_STORAGE_UNAVAILABLE | Mounts unavailable | Mounts are unavailable until the server’s storage check passes; the mount directory and the directory of the backends’ own mounts may overlap, so no mount or backend connection can start, and backend file operations are refused before the backend is looked up | Retry after the time given in Retry-After |
Example
Section titled “Example”curl -X PUT \ -H "Content-Type: application/octet-stream" \ --data-binary @./local/file.txt \ "https://{projectId}-{containerId}-files-1.{server}.containers.hoody.com/path/to/remote/file.txt?type=ssh&server=sftp.example.com:22&user=alice&pass=c2VjcmV0"import { HoodyClient } from 'hoody-sdk';import { readFileSync } from 'node:fs';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-files-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
const content = readFileSync('./local/file.txt');
await client.files.ssh.upload('/path/to/remote/file.txt', content, { server: 'sftp.example.com:22', user: 'alice', pass: 'c2VjcmV0',});Remote S3
Section titled “Remote S3”GET /{path}?type=s3
Section titled “GET /{path}?type=s3”Read an object from an S3 bucket, or list a key prefix. Works with AWS S3 and S3-compatible services such as MinIO or DigitalOcean Spaces. The response body is the object content (raw bytes) for a single key, or a listing for a key prefix.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
path | path | string | Yes | The object key or key prefix inside the bucket |
type | query | string | Yes | Backend type. Must be s3 |
server | query | string | Yes | S3 service host. An AWS host (*.amazonaws.com) is reached by s3_region; any other host is the endpoint of an S3-compatible service (MinIO, DigitalOcean Spaces, …), unless s3_endpoint names one |
s3_bucket | query | string | Yes | S3 bucket name. The path is the object key (or key prefix) within this bucket |
s3_region | query | string | Yes | S3 region, for example us-east-1 |
user | query | string | No | Access key ID. Give both user and pass, or neither for anonymous access to a public bucket |
pass | query | string | No | Secret access key (base64 encoded). Give both user and pass, or neither |
s3_endpoint | query | string | No | Endpoint of an S3-compatible service (MinIO, etc.), as a host or URL. Takes precedence over server |
Response
Section titled “Response”The object content (raw bytes) for a single key, or a listing for a key prefix. The wire format is the object or the listing; it is not a structured JSON envelope.
The request is missing required parameters (such as s3_bucket or s3_region), has a bucket name containing /, or supplies only one of user and pass.
{ "success": false, "error": "Invalid request", "code": "INVALID_PARAMETER"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INVALID_PARAMETER | Invalid request | A query parameter, a header or the request body is not one this operation accepts, or a copy or a move names a destination inside the directory it copies or moves; the message names what was refused | Correct the request |
INVALID_PATH | Invalid path | The request path is not valid: it does not decode, it has a . or .. component, it is outside the configured path prefix, or it is over the filesystem’s limits (a name over 255 bytes, a path over 4095 bytes) | Send a path of plain components under the served directory, within the filesystem’s limits |
The request is refused for this path.
{ "success": false, "error": "Access forbidden", "code": "ACCESS_FORBIDDEN"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ACCESS_FORBIDDEN | Access forbidden | The request is refused: the account may not access this path or where it leads (a symlink to a path the account may not access), the deployment has not enabled this operation (uploads, deletes, touches, permission and owner changes, renames, extraction, thumbnails), or the path is one this server does not write or follow (the served directory itself, a symlink when symlinks are not allowed, a read-only or immutable file, a pipe, a socket or a device, which has no content to read or copy) | Use an account with access to the path, or ask the deployment’s administrator to enable the operation; a pipe, a socket or a device is never read |
Nothing is at this path.
{ "success": false, "error": "Not found", "code": "RESOURCE_NOT_FOUND"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
RESOURCE_NOT_FOUND | Not found | Nothing is at this path, or the path is private to the server and answers as if absent | Check the path |
Pending uploads from before a service restart are still being processed on this backend. A Retry-After header indicates the seconds to wait before retrying.
{ "success": false, "error": "Uploads from before a service restart are still pending on this backend; retry later."}The service cannot carry out this request right now. A Retry-After header indicates the seconds to wait before retrying.
{ "success": false, "error": "Mounts are unavailable. Retry after the time given in Retry-After.", "code": "MOUNT_STORAGE_UNAVAILABLE"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
MOUNT_STORAGE_UNAVAILABLE | Mounts unavailable | Mounts are unavailable until the server’s storage check passes; the mount directory and the directory of the backends’ own mounts may overlap, so no mount or backend connection can start, and backend file operations are refused before the backend is looked up | Retry after the time given in Retry-After |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
BACKEND_UNREACHABLE | Remote backend unreachable | A read of a remote (through ?backend=, or one-off through ?type=) could not reach it: the remote did not answer within 30 seconds (504), or it refused the connection, its TLS or the credentials (502). Nothing was read. | Check the connection details and credentials (for a saved backend, GET /api/v1/backends/{id}/test, and PUT /api/v1/backends/{id} to update them), or retry once the remote is reachable |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
BACKEND_UNREACHABLE | Remote backend unreachable | A read of a remote (through ?backend=, or one-off through ?type=) could not reach it: the remote did not answer within 30 seconds (504), or it refused the connection, its TLS or the credentials (502). Nothing was read. | Check the connection details and credentials (for a saved backend, GET /api/v1/backends/{id}/test, and PUT /api/v1/backends/{id} to update them), or retry once the remote is reachable |
Example
Section titled “Example”curl -G \ --data-urlencode "type=s3" \ --data-urlencode "server=s3.amazonaws.com" \ --data-urlencode "s3_bucket=my-bucket" \ --data-urlencode "s3_region=us-east-1" \ --data-urlencode "user=AKIAIOSFODNN7EXAMPLE" \ --data-urlencode "pass=d0phbHJYVXRuRkVNSS9LN01ERU5HL2JQeFJmaUNZRVhBTVBMRUtFWQ==" \ "https://{projectId}-{containerId}-files-1.{server}.containers.hoody.com/photos/cat.jpg"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-files-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.files.s3.get('/photos/cat.jpg', { type: 's3', server: 's3.amazonaws.com', s3_bucket: 'my-bucket', s3_region: 'us-east-1', user: 'AKIAIOSFODNN7EXAMPLE', pass: Buffer.from('wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY').toString('base64'),});Remote FTP
Section titled “Remote FTP”GET /{path}?type=ftp
Section titled “GET /{path}?type=ftp”Connect to an FTP server and read a file or list a directory. The response body is the file content (raw bytes) for a file, or a directory listing for a directory.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
path | path | string | Yes | The file or directory path on the FTP server |
type | query | string | Yes | Backend type. Must be ftp |
server | query | string | Yes | FTP server hostname |
user | query | string | No | FTP username. Defaults to anonymous |
pass | query | string | No | FTP password (base64 encoded) |
ftp_secure | query | boolean | No | Use explicit FTPS (AUTH TLS on the control connection). Defaults to false |
ftp_passive | query | boolean | No | Passive mode, the only mode supported. false is refused with 400. Defaults to true |
Response
Section titled “Response”The file content (raw bytes) for a file path, or a directory listing for a directory path. The wire format is the file or the listing; it is not a structured JSON envelope.
The request has missing or invalid parameters. This includes ftp_passive=false (which is not allowed) and non-boolean values for ftp_secure or ftp_passive.
{ "success": false, "error": "Invalid request", "code": "INVALID_PARAMETER"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INVALID_PARAMETER | Invalid request | A query parameter, a header or the request body is not one this operation accepts, or a copy or a move names a destination inside the directory it copies or moves; the message names what was refused | Correct the request |
INVALID_PATH | Invalid path | The request path is not valid: it does not decode, it has a . or .. component, it is outside the configured path prefix, or it is over the filesystem’s limits (a name over 255 bytes, a path over 4095 bytes) | Send a path of plain components under the served directory, within the filesystem’s limits |
The request is refused for this path.
{ "success": false, "error": "Access forbidden", "code": "ACCESS_FORBIDDEN"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ACCESS_FORBIDDEN | Access forbidden | The request is refused: the account may not access this path or where it leads (a symlink to a path the account may not access), the deployment has not enabled this operation (uploads, deletes, touches, permission and owner changes, renames, extraction, thumbnails), or the path is one this server does not write or follow (the served directory itself, a symlink when symlinks are not allowed, a read-only or immutable file, a pipe, a socket or a device, which has no content to read or copy) | Use an account with access to the path, or ask the deployment’s administrator to enable the operation; a pipe, a socket or a device is never read |
Nothing is at this path.
{ "success": false, "error": "Not found", "code": "RESOURCE_NOT_FOUND"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
RESOURCE_NOT_FOUND | Not found | Nothing is at this path, or the path is private to the server and answers as if absent | Check the path |
Pending uploads from before a service restart are still being processed on this backend. A Retry-After header indicates the seconds to wait before retrying.
{ "success": false, "error": "Uploads from before a service restart are still pending on this backend; retry later."}The service cannot carry out this request right now. A Retry-After header indicates the seconds to wait before retrying.
{ "success": false, "error": "Mounts are unavailable. Retry after the time given in Retry-After.", "code": "MOUNT_STORAGE_UNAVAILABLE"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
MOUNT_STORAGE_UNAVAILABLE | Mounts unavailable | Mounts are unavailable until the server’s storage check passes; the mount directory and the directory of the backends’ own mounts may overlap, so no mount or backend connection can start, and backend file operations are refused before the backend is looked up | Retry after the time given in Retry-After |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
BACKEND_UNREACHABLE | Remote backend unreachable | A read of a remote (through ?backend=, or one-off through ?type=) could not reach it: the remote did not answer within 30 seconds (504), or it refused the connection, its TLS or the credentials (502). Nothing was read. | Check the connection details and credentials (for a saved backend, GET /api/v1/backends/{id}/test, and PUT /api/v1/backends/{id} to update them), or retry once the remote is reachable |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
BACKEND_UNREACHABLE | Remote backend unreachable | A read of a remote (through ?backend=, or one-off through ?type=) could not reach it: the remote did not answer within 30 seconds (504), or it refused the connection, its TLS or the credentials (502). Nothing was read. | Check the connection details and credentials (for a saved backend, GET /api/v1/backends/{id}/test, and PUT /api/v1/backends/{id} to update them), or retry once the remote is reachable |
Example
Section titled “Example”curl -G \ --data-urlencode "type=ftp" \ --data-urlencode "server=ftp.example.com" \ --data-urlencode "user=alice" \ --data-urlencode "pass=c2VjcmV0" \ "https://{projectId}-{containerId}-files-1.{server}.containers.hoody.com/path/to/file.txt"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-files-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.files.ftp.get('/path/to/file.txt', { type: 'ftp', server: 'ftp.example.com', user: 'alice', pass: 'c2VjcmV0',});