Backend Management
Section titled “Backend Management”Use these endpoints to list, inspect, test, update, and disconnect storage backends connected to a container’s files service. A backend represents a remote filesystem or object store (such as SSH, S3, MEGA, or Google Drive) that has been provisioned for the container. These operations cover the full lifecycle after a backend has been connected: viewing all backends, retrieving details, verifying connectivity, rotating credentials, and removing the connection.
List backends
Section titled “List backends”GET /api/v1/backends
Section titled “GET /api/v1/backends”Returns every backend currently registered with the files service, along with its mount paths. connected is always true for a registered backend; to check that the remote answers, use the test endpoint.
This endpoint takes no parameters.
curl -X GET "https://{projectId}-{containerId}-files-1.{server}.containers.hoody.com/api/v1/backends" \ -H "Authorization: Bearer <token>"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.backends.list();{ "backends": [ { "id": "a1b2c3d4e5f60718", "backend_type": "ssh", "server": "ssh:generic", "user": "deploy", "connected": true, "created_at": "2025-01-15T08:32:11Z", "mount_paths": ["/mnt/backups"] }, { "id": "9f8e7d6c5b4a3210", "backend_type": "s3", "server": "s3:generic", "user": "", "connected": true, "created_at": "2024-11-02T14:05:43Z", "mount_paths": [] } ], "count": 2}| 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 |
Get backend details
Section titled “Get backend details”GET /api/v1/backends/{id}
Section titled “GET /api/v1/backends/{id}”Returns detailed metadata for a single backend, including its type, server, user, and mount paths.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Unique backend identifier (16-character hex) |
curl -X GET "https://{projectId}-{containerId}-files-1.{server}.containers.hoody.com/api/v1/backends/a1b2c3d4e5f60718" \ -H "Authorization: Bearer <token>"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.backends.get('a1b2c3d4e5f60718');{ "id": "a1b2c3d4e5f60718", "backend_type": "ssh", "server": "ssh:generic", "user": "deploy", "connected": true, "created_at": "2025-01-15T08:32:11Z", "mount_paths": ["/mnt/backups"]}The backend with the given ID was not found.
{ "success": false, "code": "MOUNT_STORAGE_UNAVAILABLE", "error": "Mounts are unavailable until the server's storage check passes"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
MOUNT_STORAGE_UNAVAILABLE | Mounts unavailable | Mounts are unavailable until the server’s storage check passes; retry later. The server’s storage check did not pass (for example, the mount directory and the directory of the backends’ own mounts overlap), so no mount or backend connection can start, and backend file operations are refused. The refusal comes before the mount or backend is looked up, so an unknown id is answered 503, not 404, until the check passes. Existing mounts show the error, and the mount list still answers. Other files are served as usual. Nothing was changed. | Retry after the time given in Retry-After |
Test backend connection
Section titled “Test backend connection”GET /api/v1/backends/{id}/test
Section titled “GET /api/v1/backends/{id}/test”Verifies that a backend connection is working. The service connects (or reuses the open connection) and reports whether the remote responds.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Unique backend identifier (16-character hex) |
curl -X GET "https://{projectId}-{containerId}-files-1.{server}.containers.hoody.com/api/v1/backends/a1b2c3d4e5f60718/test" \ -H "Authorization: Bearer <token>"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.backends.test('a1b2c3d4e5f60718');{ "id": "a1b2c3d4e5f60718", "status": "connected", "message": "Connection verified"}The connection could not be made. The error message describes why.
{ "success": false, "error": "The connection could not be made; the error says why"}{ "success": false, "error": "Backend not found"}A Retry-After header (in seconds) accompanies this response, indicating when to retry.
{ "success": false, "error": "Uploads from before a service restart are still pending on this backend; retry later"}{ "success": false, "code": "MOUNT_STORAGE_UNAVAILABLE", "error": "Mounts are unavailable until the server's storage check passes"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
MOUNT_STORAGE_UNAVAILABLE | Mounts unavailable | Mounts are unavailable until the server’s storage check passes; retry later. The server’s storage check did not pass (for example, the mount directory and the directory of the backends’ own mounts overlap), so no mount or backend connection can start, and backend file operations are refused. The refusal comes before the mount or backend is looked up, so an unknown id is answered 503, not 404, until the check passes. Existing mounts show the error, and the mount list still answers. Other files are served as usual. Nothing was changed. | Retry after the time given in Retry-After |
Update backend credentials
Section titled “Update backend credentials”PUT /api/v1/backends/{id}
Section titled “PUT /api/v1/backends/{id}”Rotates credentials (passwords, tokens, OAuth refresh tokens, S3 keys, passphrases) for an existing backend connection. Identity fields (host, user, port, type) cannot be changed through this endpoint; disconnect and reconnect to alter those. The backend must have no active or in-progress mounts; unmount first. No-op rotations that supply the same values as currently stored succeed without contacting the remote.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Backend ID (16-character hex string) |
Request Body
Section titled “Request Body”The body is a free-form object of credential fields. Allowed keys include pass, password, key, key_pem, passphrase, key_file_pass, token, refresh_token, auth_token, bearer_token, session_token, secret, secret_key, secret_access_key, access_key_id, client_secret, client_id, service_account_credentials, and private_key. Values must be strings or null; passing null deletes the field. On sftp, key, passphrase and password are other names for key_pem, key_file_pass and pass; a null under the other name removes nothing, so send key_pem: null to remove a key. The request body must not exceed 16 KB.
curl -X PUT "https://{projectId}-{containerId}-files-1.{server}.containers.hoody.com/api/v1/backends/a1b2c3d4e5f60718" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{"password":"new-rotated-secret"}'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.backends.update('a1b2c3d4e5f60718', { password: 'new-rotated-secret' });{ "success": true, "message": "Backend 'a1b2c3d4e5f60718' credentials updated"}{ "success": false, "error": "Invalid request: disallowed field, non-string value, both pass+password, S3 key mismatch, empty body, or malformed JSON"}{ "success": false, "error": "Backend not found"}{ "success": false, "error": "Backend has active, mounting, or unmounting mounts (unmount first), or uploads from before a restart that are still running (retry after they finish)"}{ "success": false, "error": "Request body exceeds 16 KB limit"}{ "success": false, "error": "Internal server error (registry update or mount query failure)"}{ "success": false, "error": "Credential validation failed against the remote backend"}{ "success": false, "code": "MOUNT_STORAGE_UNAVAILABLE", "error": "Mounts are unavailable until the server's storage check passes"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
MOUNT_STORAGE_UNAVAILABLE | Mounts unavailable | Mounts are unavailable until the server’s storage check passes; retry later. The server’s storage check did not pass (for example, the mount directory and the directory of the backends’ own mounts overlap), so no mount or backend connection can start, and backend file operations are refused. The refusal comes before the mount or backend is looked up, so an unknown id is answered 503, not 404, until the check passes. Existing mounts show the error, and the mount list still answers. Other files are served as usual. Nothing was changed. | Retry after the time given in Retry-After |
Disconnect backend
Section titled “Disconnect backend”DELETE /api/v1/backends/{id}
Section titled “DELETE /api/v1/backends/{id}”Removes a backend connection. The uploads query parameter controls what happens to any data that was written through the backend’s mounts and has not been uploaded yet: keep (the default) continues uploading, while discard deletes the pending uploads immediately.
The mounts of the backend are removed first. There is no wait mode on this endpoint; to wait for uploads to finish, remove the mounts with uploads=wait first, then delete the backend.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Unique backend identifier (16-character hex) |
uploads | query | string | No | What becomes of what was written on the backend’s mounts and is not uploaded yet: keep uploading it (keep) or delete it (discard). Default: keep |
curl -X DELETE "https://{projectId}-{containerId}-files-1.{server}.containers.hoody.com/api/v1/backends/a1b2c3d4e5f60718" \ -H "Authorization: Bearer <token>"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.backends.delete('a1b2c3d4e5f60718');{ "success": true, "data": { "id": "a1b2c3d4e5f60718", "removed": true, "uploads": { "delivered": false, "discarded": 5 } }, "message": "Backend 'a1b2c3d4e5f60718' disconnected"}The response has no body.
{ "success": false, "code": "INVALID_PARAMETER", "error": "`uploads` is not keep or discard (`wait` included), `wait_seconds` is given, or either is given twice. Nothing was removed."}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INVALID_PARAMETER | Invalid parameter | uploads is not keep or discard (wait included), wait_seconds is given, or either is given twice. Nothing was removed. | Send uploads=keep or discard; to wait for uploads, remove the mounts with uploads=wait first |
{ "success": false, "code": "BACKEND_NOT_FOUND", "error": "No backend exists with the specified ID"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
BACKEND_NOT_FOUND | Backend not found | No backend exists with the specified ID | Verify the backend ID is correct or list all backends |
{ "success": false, "code": "MOUNT_PATH_CHANGED", "error": "A directory on the mount path moved while the mount was being removed, so its removal was taken back. The backend and that mount are kept; the backend's other mounts may have been removed, and those stay removed."}| Error Code | Title | Description | Resolution |
|---|---|---|---|
MOUNT_PATH_CHANGED | Mount path changed | A directory on the mount path moved while the mount was being removed, so its removal was taken back. The backend and that mount are kept; the backend’s other mounts may have been removed, and those stay removed. | Retry |
MOUNT_PATH_IN_USE | Mount path in use | A files request is changing a path at or around the mount path of one of the backend’s mounts right now. The backend and that mount are kept; the backend’s other mounts may have been removed, and those stay removed. | Retry when the files request has finished |
UPLOADS_DISCARD_SHARED | Nothing of this mount alone can be discarded | With uploads=discard: a mount of the backend has no vfs_config of its own, and what its shared connection holds cannot be discarded. Nothing was removed. | Remove those mounts first, with uploads=wait or keep, then the backend with uploads=discard |
{ "success": false, "error": "A mount's path cannot be removed safely right now, or a directory on it moved while its link was removed and the link could not be put back there; the message says which. The backend and that mount are kept; the backend's other mounts may have been removed, and those stay removed. Retry later, or, for a link not put back, once the directory is back."}{ "success": false, "code": "SERVICE_STOPPING", "error": "The service is stopping, so the request was not carried out. Nothing was changed."}| Error Code | Title | Description | Resolution |
|---|---|---|---|
SERVICE_STOPPING | Service stopping | The service is stopping, so the request was not carried out. Nothing was changed. | Retry once the service is back |
MOUNT_STORAGE_UNAVAILABLE | Mounts unavailable | Mounts are unavailable until the server’s storage check passes; retry later. The server’s storage check did not pass (for example, the mount directory and the directory of the backends’ own mounts overlap), so no mount or backend connection can start, and backend file operations are refused. The refusal comes before the mount or backend is looked up, so an unknown id is answered 503, not 404, until the check passes. Existing mounts show the error, and the mount list still answers. Other files are served as usual. Nothing was changed. | Retry after the time given in Retry-After |