### Install TrueNAS API Client Source: https://context7.com/truenas/api_client/llms.txt Install the client library using pip, either a specific stable release or from a local clone. pipx can also be used for automatic virtual environment management. ```bash pip install git+https://github.com/truenas/api_client.git@TS-25.10.3 ``` ```bash git clone https://github.com/truenas/api_client.git cd api_client git checkout TS-25.10.3 pip install . ``` ```bash pipx install . ``` -------------------------------- ### midclt CLI Examples Source: https://context7.com/truenas/api_client/llms.txt Provides various command-line examples using the `midclt` tool to interact with the TrueNAS API. Covers basic operations like ping and calling methods, as well as advanced usage such as remote connections, API keys, job tracking, and subscribing to events. ```APIDOC ## `midclt` CLI `midclt` exposes the full TrueNAS API from the command line. It supports `call`, `ping`, and `subscribe` subcommands with options for remote URIs, credentials, job tracking, and reading payloads from stdin. ```bash # Ping the local middleware midclt ping # Local API call midclt call system.version # Query with JSON filter midclt call user.query '[["username","=","admin"]]' # Create a user (JSON payload inline) midclt call user.create '{"full_name":"Jane","username":"jdoe","password":"Pa$$w0rd","group_create":true}' # Remote call with username/password midclt --uri ws://nas.example.com/api/current -U admin -P s3cr3t call system.info # Remote call with API key midclt --uri wss://nas.example.com/api/current -U admin -K /etc/truenas/api_key.json call smb.config # Track a long-running job with a progress bar midclt call -j pool.dataset.lock tank/encrypted # Track a job and print status lines (suitable for systemd logs) midclt call -j -jp description pool.scrub tank 0 # Read payload from stdin (keeps secrets out of shell history) cat /etc/truenas/new_user.json | midclt -U admin -K /etc/truenas/key.json call user.create - # Subscribe to job events (print 10, then exit) midclt subscribe -n 10 core.get_jobs # Development: disable SSL verification midclt --uri wss://dev.truenas.local/api/current --insecure -K /tmp/key.json call system.info # Use a custom call timeout midclt -t 300 call interface.sync true ``` ``` -------------------------------- ### Start a Job with TrueNAS API Client Source: https://github.com/truenas/api_client/blob/master/README.md This snippet demonstrates how to start a job using the TrueNAS API client. It shows how to check if a dataset is locked and then unlock it if necessary. Ensure you have the `Client` class imported and properly configured. ```python with Client() as c: is_locked = c.call("pool.dataset.lock", "mypool/mydataset", job=True) if is_locked: args = {"datasets": [{"name": "mypool/mydataset", "passphrase": "passphrase"}]} c.call("pool.dataset.unlock", "mypool/mydataset", args, job=True) ``` -------------------------------- ### midclt: Make local API calls Source: https://github.com/truenas/api_client/blob/master/README.md Example of making a local API call using the `midclt` command to create a user. ```APIDOC ## midclt: Make local API calls ### Description Makes a local API call to create a new user on the TrueNAS system. ### Method `midclt` command ### Parameters - `call` - Command to invoke an API method. - `user.create` - The API method to call for creating a user. - `{ ``` -------------------------------- ### Basic Client.call Examples Source: https://context7.com/truenas/api_client/llms.txt Shows various ways to use the Client.call method, including simple reads, calls with arguments, queries with filters, resource creation, and handling exceptions. ```python from truenas_api_client import Client, ClientException, ValidationErrors, CallTimeout with Client() as c: # Simple read call version = c.call("system.version") print(version) # e.g., "TrueNAS-25.10.3" # Call with arguments user = c.call("user.get_instance", 70) print(user["username"]) # Query with filter admins = c.call("user.query", [["username", "=", "admin"]]) # Create resource (raises ValidationErrors on bad input) try: new_user_id = c.call("user.create", { "full_name": "Jane Doe", "username": "jdoe", "password": "Str0ngP@ss!", "group_create": True, }) print(f"Created user with id {new_user_id}") except ValidationErrors as e: print(f"Validation failed:\n{e}") except ClientException as e: print(f"API error [{e.errno}]: {e.error}") # Per-call timeout override try: result = c.call("some.slow.method", timeout=300) except CallTimeout: print("Call timed out after 300 seconds") # Background call (fire-and-forget; returns Call object immediately) call_obj = c.call("interface.sync", background=True) # ... do other work ... result = c.wait(call_obj) ``` -------------------------------- ### TNScramClient Usage Example Source: https://github.com/truenas/api_client/blob/master/truenas_api_client/py_scram/README.md Demonstrates how to instantiate and use the TNScramClient for SCRAM authentication, either with raw password material or pre-computed keys. ```python from truenas_api_client.scram_impl import TNScramClient # Create client with password client = TNScramClient(raw_key_material="password123") # Or with pre-computed keys client = TNScramClient( client_key=client_key, stored_key=stored_key, server_key=server_key ) # Follow SCRAM authentication flow msg1 = client.get_client_first_message(username="user") # ... exchange messages with server ... msg2 = client.get_client_final_message(server_response) # ... exchange messages with server ... verified = client.verify_server_final_message(server_response) ``` -------------------------------- ### Start a Job with midclt Source: https://github.com/truenas/api_client/blob/master/README.md Initiate a job using the `-j` flag with the `midclt call` command. This is useful for long-running operations where you don't need to wait for completion. ```bash root@my_truenas[~]# midclt call -j pool.dataset.lock mypool/mydataset ``` -------------------------------- ### Two-Factor Authentication with TrueNAS API Source: https://github.com/truenas/api_client/blob/master/examples/README.md This script demonstrates how to perform two-factor authentication using the Python API client and pytotp. Ensure you have the 'pyotp' library installed (`pip install pyotp`). ```python import os import sys import json import pyotp from websocket import create_connection # Set to True to enable debug output DEBUG = False def debug_print(msg): if DEBUG: print(f"[DEBUG] {msg}") def main(): # Get TrueNAS hostname, API key, and 2FA secret from environment variables truenas_host = os.environ.get('TRUENAS_HOST') truenas_api_key = os.environ.get('TRUENAS_API_KEY') two_factor_secret = os.environ.get('TWO_FACTOR_SECRET') if not truenas_host or not truenas_api_key or not two_factor_secret: print("Error: TRUENAS_HOST, TRUENAS_API_KEY, and TWO_FACTOR_SECRET environment variables must be set.") sys.exit(1) # Construct the websocket URL ws_url = f"wss://{truenas_host}/api/v2.0/websocket" try: # Establish a websocket connection ws = create_connection(ws_url, header={"Authorization": f"Bearer {truenas_api_key}"}) debug_print("Websocket connection established.") # Generate the current TOTP code totp = pyotp.TOTP(two_factor_secret) two_factor_code = totp.now() debug_print(f"Generated 2FA code: {two_factor_code}") # --- Perform Two-Factor Authentication Challenge-Response --- payload_auth = { "id": 1, "method": "auth.two_factor.authenticate", "params": [ two_factor_code ] } ws.send(json.dumps(payload_auth)) result_auth = ws.recv() debug_print(f"Authentication result: {result_auth}") result_auth_json = json.loads(result_auth) if result_auth_json.get("error"): print(f"Authentication failed: {result_auth_json['error']}") sys.exit(1) print("Successfully authenticated with two-factor code.") except Exception as e: print(f"An error occurred: {e}") sys.exit(1) finally: if 'ws' in locals() and ws: ws.close() debug_print("Websocket connection closed.") if __name__ == "__main__": main() ``` -------------------------------- ### midclt CLI: Local API Call Source: https://context7.com/truenas/api_client/llms.txt Execute a local API call using `midclt call`. This example retrieves the system version. ```bash midclt call system.version ``` -------------------------------- ### Create SMB Time Machine Share with TrueNAS API Source: https://github.com/truenas/api_client/blob/master/examples/README.md This script provides a basic example of how to create a new ZFS dataset and share it as a MacOS Time Machine target using the TrueNAS API. ```python import os import sys import json from websocket import create_connection # Set to True to enable debug output DEBUG = False def debug_print(msg): if DEBUG: print(f"[DEBUG] {msg}") def main(): # Get TrueNAS hostname and API key from environment variables truenas_host = os.environ.get('TRUENAS_HOST') truenas_api_key = os.environ.get('TRUENAS_API_KEY') if not truenas_host or not truenas_api_key: print("Error: TRUENAS_HOST and TRUENAS_API_KEY environment variables must be set.") sys.exit(1) # Construct the websocket URL ws_url = f"wss://{truenas_host}/api/v2.0/websocket" try: # Establish a websocket connection ws = create_connection(ws_url, header={"Authorization": f"Bearer {truenas_api_key}"}) debug_print("Websocket connection established.") # --- Create ZFS Dataset --- dataset_name = "pool1/timemachine_test" debug_print(f"Creating ZFS dataset: {dataset_name}") payload_dataset = { "id": 1, "method": "core.create_dataset", "params": [ { "name": dataset_name, "type": "FILESYSTEM", "sparse": True } ] } ws.send(json.dumps(payload_dataset)) result_dataset = ws.recv() debug_print(f"ZFS dataset creation result: {result_dataset}") result_dataset_json = json.loads(result_dataset) if result_dataset_json.get("error"): print(f"Error creating dataset: {result_dataset_json['error']}") # Continue to attempt share creation even if dataset creation fails, in case it already exists # --- Create SMB Share --- share_name = "TimeMachineShare" debug_print(f"Creating SMB share: {share_name} for dataset {dataset_name}") payload_share = { "id": 2, "method": "sharing.smb.create", "params": [ { "path": f"/{dataset_name}", "name": share_name, "purpose": "SMART_ARRAY_SHARE", "comment": "Time Machine Share", "timemachine": True, "guestok": False, "ro": False, "browsable": True, "recycle": True, "ப்பத": True } ] } ws.send(json.dumps(payload_share)) result_share = ws.recv() debug_print(f"SMB share creation result: {result_share}") result_share_json = json.loads(result_share) if result_share_json.get("error"): print(f"Error creating SMB share: {result_share_json['error']}") sys.exit(1) print(f"Successfully created SMB share '{share_name}' for Time Machine.") except Exception as e: print(f"An error occurred: {e}") sys.exit(1) finally: if 'ws' in locals() and ws: ws.close() debug_print("Websocket connection closed.") if __name__ == "__main__": main() ``` -------------------------------- ### midclt CLI: Subscribe to Job Events Source: https://context7.com/truenas/api_client/llms.txt Subscribe to job-related events and limit the number of events received. This example subscribes to `core.get_jobs` and exits after receiving 10 events using the `-n 10` option. ```bash midclt subscribe -n 10 core.get_jobs ``` -------------------------------- ### midclt CLI: Query with JSON Filter Source: https://context7.com/truenas/api_client/llms.txt Perform an API query with a JSON-formatted filter. This example retrieves users whose username is 'admin'. Ensure the JSON filter is correctly escaped for the shell. ```bash midclt call user.query '[["username","=","admin"]]' ``` -------------------------------- ### midclt CLI: Custom Call Timeout Source: https://context7.com/truenas/api_client/llms.txt Set a custom timeout for API calls using the `-t` option. This example sets a timeout of 300 seconds for the `interface.sync` call. ```bash midclt -t 300 call interface.sync true ``` -------------------------------- ### midclt CLI: Track Long-Running Job Source: https://context7.com/truenas/api_client/llms.txt Monitor the progress of a long-running API job using the `-j` flag. This example locks a pool dataset and displays a progress bar. ```bash midclt call -j pool.dataset.lock tank/encrypted ``` -------------------------------- ### Login with API Key and System Info Source: https://context7.com/truenas/api_client/llms.txt Demonstrates logging in using an API key from a JSON file and retrieving system information. ```python from truenas_api_client import Client with Client(uri="wss://nas.example.com/api/current") as c: # Any of the above paths work transparently c.login_with_api_key("admin", "/etc/truenas/scram_key.json") info = c.call("system.info") print(f"Connected to TrueNAS {info['version']} on {info['hostname']}") ``` -------------------------------- ### midclt CLI: Create User with Inline JSON Source: https://context7.com/truenas/api_client/llms.txt Create a new user by providing the user details as an inline JSON object to the `user.create` call. This method is convenient for simple user creations. ```bash midclt call user.create '{"full_name":"Jane","username":"jdoe","password":"Pa$$w0rd","group_create":true}' ``` -------------------------------- ### Subscribe and Unsubscribe to Events Source: https://context7.com/truenas/api_client/llms.txt Demonstrates how to subscribe to real-time middleware events using Client.subscribe and unsubscribe using Client.unsubscribe, with a callback for processing event notifications. ```python from threading import Event as ThreadEvent from truenas_api_client import Client done = ThreadEvent() seen = [] def on_job_event(mtype: str, **message): """Called for every core.get_jobs collection update.""" fields = message.get("fields", {}) state = fields.get("state") print(f"[{mtype}] job={message.get('id')} state={state} " f"progress={fields.get('progress', {}).get('percent')}%)" seen.append(mtype) if len(seen) >= 5: done.set() with Client(uri="wss://nas.example.com/api/current") as c: c.login_with_api_key("admin", "/etc/truenas/api_key.json") # Subscribe and get the subscription ID back sub_id = c.subscribe("core.get_jobs", on_job_event) # Trigger a job so we receive events c.call("pool.dataset.lock", "tank/data", job=True) # Wait up to 60 s for 5 events, then unsubscribe done.wait(timeout=60) c.unsubscribe(sub_id) ``` -------------------------------- ### Client Initialization Source: https://context7.com/truenas/api_client/llms.txt Initialize the Client for local or remote connections. The Client automatically selects the appropriate connection type based on the URI. It can be used as a context manager for automatic connection closing. ```APIDOC ## Client Initialization `Client(uri, reserved_ports, private_methods, py_exceptions, log_py_exceptions, call_timeout, verify_ssl)` is the primary entry point. It automatically selects `JSONRPCClient` for `/api/current` URIs and `LegacyClient` for `/websocket` URIs. When used as a context manager, the connection is closed automatically on exit. ```python from truenas_api_client import Client, ClientException # Local connection (Unix socket — default when run on TrueNAS) with Client() as c: print(c.ping()) # pong # Remote JSON-RPC 2.0 connection (TrueNAS 25.04+) with Client(uri="wss://nas.example.com/api/current", verify_ssl=True) as c: c.login_with_password("admin", "s3cr3t") info = c.call("system.info") print(info["hostname"]) # Remote legacy connection (TrueNAS 24.10 and earlier) with Client(uri="ws://nas.example.com/websocket") as c: c.login_with_password("root", "oldpassword") print(c.call("system.version")) # Explicit timeout + SSL verification disabled (dev/test only) with Client( uri="wss://dev.nas.local/api/current", call_timeout=120, verify_ssl=False, ) as c: c.login_with_api_key("admin", "1-rawkeystring") datasets = c.call("pool.dataset.query") ``` ``` -------------------------------- ### Subscribe to Events Source: https://context7.com/truenas/api_client/llms.txt Demonstrates how to subscribe to events, such as alerts, and process them using a callback function. It also shows how to wait for a specific event or timeout. ```APIDOC ## Subscribe using a pre-built payload (allows waiting on the Event) ```python from truenas_api_client import Client with Client(uri="wss://nas.example.com/api/current") as c: c.login_with_password("admin", "s3cr3t") payload = c.event_payload() def on_alert(mtype, **msg): print(f"Alert [{mtype}]: {msg}") c.subscribe("alert.list", on_alert, payload) payload["event"].wait(timeout=30) # unblocks on notify_unsubscribed ``` ``` -------------------------------- ### midclt CLI: Track Job with Status Lines Source: https://context7.com/truenas/api_client/llms.txt Track a job and print status updates line by line, which is suitable for logging systems like systemd. The `-jp description` option includes a description of the job's status. ```bash midclt call -j -jp description pool.scrub tank 0 ``` -------------------------------- ### midclt with API Key and Stdin Payload Source: https://github.com/truenas/api_client/blob/master/README.md Use an API key from a file with the `-K` option and read the API call payload from standard input using `-`. This enhances security by keeping sensitive data out of command history. ```bash # API key from file, payload from stdin via input redirection midclt -U larry -K /home/larry/truenas_api_key.json call user.create - < /home/larry/user_secret_payload.json ``` ```bash # API key from file, payload from stdin via pipe cat /path/to/secret/payload.json | midclt -U admin -K /root/.truenas_api_key call user.create - ``` -------------------------------- ### Create SMB Time Machine Share with Python Source: https://context7.com/truenas/api_client/llms.txt A complete script to remotely connect, authenticate using an API key, enable SMB Apple extensions, create a ZFS dataset, and configure it as an SMB Time Machine share. ```python from truenas_api_client import Client DS_NAME = "tank/timemachine" SHARE_NAME = "TimeMachine" API_USERNAME = "admin" API_KEY_FILE = "/etc/truenas/api_key.json" # JSON or raw key string with Client("wss://nas.example.com/api/current") as c: c.login_with_api_key(API_USERNAME, API_KEY_FILE) # Enable Apple (AAPL) SMB extensions if not already set smb_config = c.call("smb.config") if not smb_config["aapl_extensions"]: c.call("smb.update", {"aapl_extensions": True}) # Create the ZFS dataset with SMB share_type ds = c.call("pool.dataset.create", { "name": DS_NAME, "share_type": "SMB", }) # Create the SMB share as a Time Machine target share = c.call("sharing.smb.create", { "path": ds["mountpoint"], "name": SHARE_NAME, "purpose": "TIMEMACHINE", }) print(f"Time Machine share '{share['name']}' created at {ds['mountpoint']}") ``` -------------------------------- ### Make Local API Calls with Python Client Source: https://github.com/truenas/api_client/blob/master/README.md Utilize the Python API client for making local API calls. Instantiate the `Client` and use the `call` method with the API method name and arguments. ```python from truenas_api_client import Client with Client() as c: user = {"full_name": "John Doe", "username": "jdoe", "password": "Canary", "group_create": True} new_user_entry = c.call("user.create", user) print(new_user_entry["full_name"]) # John Doe ``` -------------------------------- ### Login to Remote TrueNAS with midclt Source: https://github.com/truenas/api_client/blob/master/README.md Connect to a remote TrueNAS instance using the `--uri` option and authenticate with a username and password. The `call` subcommand then executes the specified API method. ```bash root@my_truenas[~]# midclt --uri ws://some.other.truenas/api/current -U user -P password call system.info ``` -------------------------------- ### Login with Password Source: https://context7.com/truenas/api_client/llms.txt Authenticate using username and password, with optional TOTP for two-factor accounts. Falls back to legacy auth for pre-25.04 servers. ```python from truenas_api_client import Client with Client(uri="wss://nas.example.com/api/current") as c: # Simple password login c.login_with_password("admin", "mypassword") # With two-factor authentication (TOTP) try: c.login_with_password("admin", "mypassword", otp_token="123456") except ValueError as e: # Handles: OTP_REQUIRED, EXPIRED, DENIED, REDIRECT, invalid credentials print(f"Login failed: {e}") print(c.call("system.info")["hostname"]) ``` -------------------------------- ### Make Local API Calls with midclt Source: https://github.com/truenas/api_client/blob/master/README.md Execute local API calls directly from the TrueNAS console using the `midclt call` command. Provide the API method and its arguments as a JSON string. ```bash root@my_truenas[~]# midclt call user.create '{"full_name": "John Doe", "username": "user", "password": "pass", "group_create": true}' ``` -------------------------------- ### SCRAM Key Derivation Formulas Source: https://github.com/truenas/api_client/blob/master/truenas_api_client/py_scram/README.md Illustrates the key derivation process using PBKDF2 and HMAC for SCRAM authentication. ```plaintext SaltedPassword := PBKDF2(Normalize(password), salt, iterations) ClientKey := HMAC(SaltedPassword, "Client Key") StoredKey := H(ClientKey) ServerKey := HMAC(SaltedPassword, "Server Key") ClientSignature := HMAC(StoredKey, AuthMessage) ClientProof := ClientKey XOR ClientSignature ServerSignature := HMAC(ServerKey, AuthMessage) ``` -------------------------------- ### Client.call with Jobs Source: https://context7.com/truenas/api_client/llms.txt Illustrates using Client.call for long-running operations, including waiting for job completion, handling job results, and using callbacks for progress updates. ```python from truenas_api_client import Client, ClientException, ValidationErrors with Client() as c: c.login_with_password("admin", "s3cr3t") # Wait for job completion; returns final result is_locked = c.call("pool.dataset.lock", "tank/encrypted", job=True) print(f"Dataset locked: {is_locked}") # Unlock with passphrase args = {"datasets": [{"name": "tank/encrypted", "passphrase": "my-passphrase"}]} result = c.call("pool.dataset.unlock", "tank/encrypted", args, job=True) # Job with live progress callback def on_progress(job): pct = job["progress"]["percent"] desc = job["progress"]["description"] if pct is not None: print(f" [{pct:.0f}%] {desc}") result = c.call("pool.scrub", "tank", 0, job=True, callback=on_progress) # Get back a Job object for manual waiting job_obj = c.call("replication.run", 1, job="RETURN") print(f"Job started: {job_obj}") try: final = job_obj.result() # blocks until SUCCESS/FAILED/ABORTED print(f"Replication result: {final}") except ClientException as e: print(f"Job failed: {e}") ``` -------------------------------- ### Initialize TrueNAS API Client Source: https://context7.com/truenas/api_client/llms.txt Initialize the Client for local or remote connections. Use as a context manager for automatic connection closing. Supports JSON-RPC 2.0 and legacy URIs. ```python from truenas_api_client import Client, ClientException # Local connection (Unix socket — default when run on TrueNAS) with Client() as c: print(c.ping()) # pong ``` ```python # Remote JSON-RPC 2.0 connection (TrueNAS 25.04+) with Client(uri="wss://nas.example.com/api/current", verify_ssl=True) as c: c.login_with_password("admin", "s3cr3t") info = c.call("system.info") print(info["hostname"]) ``` ```python # Remote legacy connection (TrueNAS 24.10 and earlier) with Client(uri="ws://nas.example.com/websocket") as c: c.login_with_password("root", "oldpassword") print(c.call("system.version")) ``` ```python # Explicit timeout + SSL verification disabled (dev/test only) with Client( uri="wss://dev.nas.local/api/current", call_timeout=120, verify_ssl=False, ) as c: c.login_with_api_key("admin", "1-rawkeystring") datasets = c.call("pool.dataset.query") ``` -------------------------------- ### Login with API Key Source: https://context7.com/truenas/api_client/llms.txt Authenticate using an API key string or file path. Supports SCRAM-SHA-512 (TrueNAS 26+) and plain API key authentication. ```python from truenas_api_client import Client, APIKeyAuthMech # Using raw key string with Client(uri="wss://nas.example.com/api/current") as c: c.login_with_api_key("admin", "1-uz8DhKHFhRIUQIvjzabPYtpy5wf1DJ3ZBLlDgNVhRAFT7Y6pJGUlm0n3apwxWEU4") print(c.call("auth.me")["username"]) ``` ```python # Using JSON key file (auto-detected by absolute path) with Client(uri="wss://nas.example.com/api/current") as c: c.login_with_api_key("admin", "/etc/truenas/api_key.json") users = c.call("user.query") ``` ```python # Force plain auth (older servers without SCRAM support) with Client(uri="wss://nas.example.com/api/current") as c: c.login_with_api_key("admin", "/etc/truenas/api_key.json", auth_mechanism=APIKeyAuthMech.PLAIN) ``` ```python # Force SCRAM (raises ValueError if server does not support it) with Client(uri="wss://nas.example.com/api/current") as c: c.login_with_api_key("admin", "/etc/truenas/api_key.json", auth_mechanism=APIKeyAuthMech.SCRAM) ``` -------------------------------- ### API Key Storage Formats Source: https://context7.com/truenas/api_client/llms.txt The library accepts API keys in raw string, JSON file, or INI file formats. These can be passed directly or loaded from a file via an absolute path. ```python # 1. Raw string (format: {id}-{key_material}) raw_key = "1-uz8DhKHFhRIUQIvjzabPYtpy5wf1DJ3ZBLlDgNVhRAFT7Y6pJGUlm0n3apwxWEU4" ``` -------------------------------- ### Client.login_with_api_key Source: https://context7.com/truenas/api_client/llms.txt Authenticates using an API key, which can be provided as a raw string or a path to a key file. The method prioritizes SCRAM-SHA-512 authentication for TrueNAS 26+ servers, falling back to plain API key authentication for older versions. ```APIDOC ## `Client.login_with_api_key` Authenticates using an API key string or path to a key file. Automatically uses SCRAM-SHA-512 if the server supports it (TrueNAS 26+), falling back to plain API key auth. ```python from truenas_api_client import Client, APIKeyAuthMech # Using raw key string with Client(uri="wss://nas.example.com/api/current") as c: c.login_with_api_key("admin", "1-uz8DhKHFhRIUQIvjzabPYtpy5wf1DJ3ZBLlDgNVhRAFT7Y6pJGUlm0n3apwxWEU4") print(c.call("auth.me")["username"]) # Using JSON key file (auto-detected by absolute path) with Client(uri="wss://nas.example.com/api/current") as c: c.login_with_api_key("admin", "/etc/truenas/api_key.json") users = c.call("user.query") # Force plain auth (older servers without SCRAM support) with Client(uri="wss://nas.example.com/api/current") as c: c.login_with_api_key("admin", "/etc/truenas/api_key.json", auth_mechanism=APIKeyAuthMech.PLAIN) # Force SCRAM (raises ValueError if server does not support it) with Client(uri="wss://nas.example.com/api/current") as c: c.login_with_api_key("admin", "/etc/truenas/api_key.json", auth_mechanism=APIKeyAuthMech.SCRAM) ``` ``` -------------------------------- ### midclt CLI: Ping Local Middleware Source: https://context7.com/truenas/api_client/llms.txt Use the `midclt ping` command to quickly check the connectivity to the local TrueNAS middleware. This is a simple way to verify that the API service is running and accessible. ```bash midclt ping ``` -------------------------------- ### Client.subscribe / Client.unsubscribe Source: https://context7.com/truenas/api_client/llms.txt Allows subscribing to real-time middleware events and receiving notifications via a callback function. Useful for monitoring system changes. ```APIDOC ## Client.subscribe / Client.unsubscribe Subscribe to real-time middleware events, providing a callback that is invoked on every matching notification. Useful for monitoring jobs, pool state, interface changes, etc. ```python from threading import Event as ThreadEvent from truenas_api_client import Client done = ThreadEvent() seen = [] def on_job_event(mtype: str, **message): """Called for every core.get_jobs collection update.""" fields = message.get("fields", {}) state = fields.get("state") print(f"[{mtype}] job={message.get('id')} state={state} " f"progress={fields.get('progress', {}).get('percent')}% ") seen.append(mtype) if len(seen) >= 5: done.set() with Client(uri="wss://nas.example.com/api/current") as c: c.login_with_api_key("admin", "/etc/truenas/api_key.json") # Subscribe and get the subscription ID back sub_id = c.subscribe("core.get_jobs", on_job_event) # Trigger a job so we receive events c.call("pool.dataset.lock", "tank/data", job=True) # Wait up to 60 s for 5 events, then unsubscribe done.wait(timeout=60) c.unsubscribe(sub_id) ``` ``` -------------------------------- ### Client.login_with_password Source: https://context7.com/truenas/api_client/llms.txt Authenticates using a username and password, optionally including a TOTP token for two-factor authentication. This method automatically falls back to the legacy `auth.login` endpoint for servers older than TrueNAS 25.04. ```APIDOC ## `Client.login_with_password` Authenticates using a username and password, with optional TOTP one-time password for two-factor accounts. Automatically falls back to the legacy `auth.login` endpoint for pre-25.04 servers. ```python from truenas_api_client import Client with Client(uri="wss://nas.example.com/api/current") as c: # Simple password login c.login_with_password("admin", "mypassword") # With two-factor authentication (TOTP) try: c.login_with_password("admin", "mypassword", otp_token="123456") except ValueError as e: # Handles: OTP_REQUIRED, EXPIRED, DENIED, REDIRECT, invalid credentials print(f"Login failed: {e}") print(c.call("system.info")["hostname"]) ``` ``` -------------------------------- ### Two-Factor Authentication (TOTP) Source: https://context7.com/truenas/api_client/llms.txt Provides methods for handling Two-Factor Authentication (TOTP) during login. It includes a high-level helper that automatically handles the `OTP_REQUIRED` challenge and a manual flow using `auth.login_ex` and `auth.login_ex_continue` for more control. ```APIDOC ## Two-Factor Authentication (TOTP) For accounts with 2FA enabled, `login_with_password` handles the `OTP_REQUIRED` challenge transparently when `otp_token` is supplied; the raw `auth.login_ex` / `auth.login_ex_continue` flow is also available. ```python import truenas_api_client # pip install pyotp # import pyotp USERNAME = "admin" PASSWORD = "s3cr3t" TOTP_SECRET_FILE = "/etc/truenas/totp_secret" def get_totp_token(secret_file: str) -> str: with open(secret_file) as f: secret = f.read().strip() # return pyotp.TOTP(secret).now() raise NotImplementedError("Install pyotp and uncomment the line above") with truenas_api_client.Client("wss://nas.example.com/api/current") as c: # Option A: high-level helper — handles OTP_REQUIRED automatically try: otp = get_totp_token(TOTP_SECRET_FILE) c.login_with_password(USERNAME, PASSWORD, otp_token=otp) except ValueError as e: print(f"Auth failed: {e}") # Option B: manual auth.login_ex flow for full control resp = c.call("auth.login_ex", { "mechanism": "PASSWORD_PLAIN", "username": USERNAME, "password": PASSWORD, }) if resp["response_type"] == "OTP_REQUIRED": otp = get_totp_token(TOTP_SECRET_FILE) resp = c.call("auth.login_ex_continue", { "mechanism": "OTP_TOKEN", "otp_token": otp, }) assert resp["response_type"] == "SUCCESS", f"Unexpected: {resp['response_type']}" print(c.call("auth.me")["username"]) ``` ``` -------------------------------- ### API Key Storage Formats Source: https://context7.com/truenas/api_client/llms.txt The library supports API keys in three formats: raw string, JSON file, or INI file. These can be passed directly to authentication methods or loaded from a file using its absolute path. ```APIDOC ## API Key Storage Formats The library accepts API keys in three formats: raw string, JSON file, or INI file. These can be passed directly or loaded from a file via an absolute path. ```python # 1. Raw string (format: {id}-{key_material}) raw_key = "1-uz8DhKHFhRIUQIvjzabPYtpy5wf1DJ3ZBLlDgNVhRAFT7Y6pJGUlm0n3apwxWEU4" ``` ``` -------------------------------- ### midclt CLI: Remote Call with API Key Source: https://context7.com/truenas/api_client/llms.txt Connect to a remote TrueNAS instance using an API key for authentication. The `--uri` and `-K` options specify the connection endpoint and the path to the API key file. ```bash midclt --uri wss://nas.example.com/api/current -U admin -K /etc/truenas/api_key.json call smb.config ``` -------------------------------- ### Two-Factor Authentication (TOTP) with TrueNAS API Source: https://context7.com/truenas/api_client/llms.txt Handles accounts with Two-Factor Authentication (2FA) enabled. The `login_with_password` helper automatically manages the `OTP_REQUIRED` challenge when `otp_token` is provided. Alternatively, the raw `auth.login_ex` and `auth.login_ex_continue` flow offers more control. ```python import truenas_api_client # pip install pyotp # import pyotp USERNAME = "admin" PASSWORD = "s3cr3t" TOTP_SECRET_FILE = "/etc/truenas/totp_secret" def get_totp_token(secret_file: str) -> str: with open(secret_file) as f: secret = f.read().strip() # return pyotp.TOTP(secret).now() raise NotImplementedError("Install pyotp and uncomment the line above") with truenas_api_client.Client("wss://nas.example.com/api/current") as c: # Option A: high-level helper — handles OTP_REQUIRED automatically try: otp = get_totp_token(TOTP_SECRET_FILE) c.login_with_password(USERNAME, PASSWORD, otp_token=otp) except ValueError as e: print(f"Auth failed: {e}") # Option B: manual auth.login_ex flow for full control resp = c.call("auth.login_ex", { "mechanism": "PASSWORD_PLAIN", "username": USERNAME, "password": PASSWORD, }) if resp["response_type"] == "OTP_REQUIRED": otp = get_totp_token(TOTP_SECRET_FILE) resp = c.call("auth.login_ex_continue", { "mechanism": "OTP_TOKEN", "otp_token": otp, }) assert resp["response_type"] == "SUCCESS", f"Unexpected: {resp['response_type']}" print(c.call("auth.me")["username"]) ``` -------------------------------- ### midclt CLI: Remote Call with Username/Password Source: https://context7.com/truenas/api_client/llms.txt Make an API call to a remote TrueNAS instance using username and password authentication. The `--uri`, `-U`, and `-P` options specify the connection details. ```bash midclt --uri ws://nas.example.com/api/current -U admin -P s3cr3t call system.info ``` -------------------------------- ### Python Client Login Methods Source: https://github.com/truenas/api_client/blob/master/README.md Authenticate the Python API client using either a username and password or an API key. The `login_with_password` and `login_with_api_key` methods handle authentication. ```python # User account with Client(uri="ws://some.other.truenas/api/current") as c: c.login_with_password(username, password) # API key with Client(uri="ws://some.other.truenas/api/current") as c: c.login_with_api_key(username, key) # API key file with Client(uri="ws://some.other.truenas/api/current") as c: c.login_with_api_key(username, "/path/to/keyfile.json") ``` -------------------------------- ### Handle API Errors with Python Client Source: https://context7.com/truenas/api_client/llms.txt Demonstrates how to catch and process `ClientException`, `ValidationErrors`, and `CallTimeout` when interacting with the TrueNAS API. `ValidationErrors` provides field-specific feedback from the server. ```python from truenas_api_client import Client, ClientException, ValidationErrors, CallTimeout from truenas_api_client.exc import ErrnoMixin with Client(uri="wss://nas.example.com/api/current") as c: c.login_with_password("admin", "s3cr3t") try: result = c.call("user.create", { "username": "x" * 200, # too long — triggers ValidationErrors "password": "", # empty — another validation error "group_create": True, }) except ValidationErrors as e: # e.errors is a list of ErrorExtra(attribute, errmsg, errcode) for err in e.errors: print(f" Field '{err.attribute}': {err.errmsg}") except CallTimeout: print("Request timed out") except ClientException as e: errname = ErrnoMixin._get_errname(e.errno) if e.errno else "UNKNOWN" print(f"API error [{errname}]: {e.error}") if e.trace: print(e.trace["formatted"]) if e.extra: import pprint pprint.pprint(e.extra) ``` -------------------------------- ### Client.ping Source: https://context7.com/truenas/api_client/llms.txt Sends a `core.ping` request to verify connectivity to the TrueNAS middleware. It returns 'pong' on success or raises `CallTimeout` if no response is received within the specified timeout. ```APIDOC ## `Client.ping` Sends `core.ping` to verify connectivity to the middleware without requiring authentication. Returns `'pong'` on success and raises `CallTimeout` if the server does not respond within the timeout. ```python from truenas_api_client import Client, CallTimeout with Client(uri="wss://nas.example.com/api/current") as c: try: result = c.ping(timeout=5) print(result) # pong except CallTimeout: print("Middleware did not respond within 5 seconds") ``` ``` -------------------------------- ### Ping TrueNAS Middleware for Connectivity Source: https://context7.com/truenas/api_client/llms.txt Use the `ping` method to check if the TrueNAS middleware is reachable and responsive. This operation does not require authentication. A `CallTimeout` exception is raised if the server does not respond within the specified timeout. ```python from truenas_api_client import Client, CallTimeout with Client(uri="wss://nas.example.com/api/current") as c: try: result = c.ping(timeout=5) print(result) # pong except CallTimeout: print("Middleware did not respond within 5 seconds") ``` -------------------------------- ### API Key INI Format Storage Source: https://github.com/truenas/api_client/blob/master/README.md API keys can be stored in INI-style configuration files. The `[TRUENAS_API_KEY]` section header is required for files with multiple sections. ```ini [TRUENAS_API_KEY] client_key = base64_encoded_client_key stored_key = base64_encoded_stored_key server_key = base64_encoded_server_key api_key_id = 1 ``` -------------------------------- ### Subscribe to Events with a Pre-built Payload Source: https://context7.com/truenas/api_client/llms.txt Subscribe to a specific event type and process incoming messages. The `event.wait()` method can be used to block until an event occurs or a timeout is reached. ```python with Client(uri="wss://nas.example.com/api/current") as c: c.login_with_password("admin", "s3cr3t") payload = c.event_payload() def on_alert(mtype, **msg): print(f"Alert [{mtype}]: {msg}") c.subscribe("alert.list", on_alert, payload) payload["event"].wait(timeout=30) # unblocks on notify_unsubscribed ``` -------------------------------- ### Client.call with Jobs Source: https://context7.com/truenas/api_client/llms.txt Handles long-running operations that return a job ID. Allows waiting for completion, returning the job result, or managing the job manually. ```APIDOC ## Client.call with Jobs Long-running operations return a job ID. Pass `job=True` to wait for completion and return the job result; pass `job='RETURN'` to get a `Job` object for manual control. An optional `callback` receives progress updates. ```python from truenas_api_client import Client, ClientException, ValidationErrors with Client() as c: c.login_with_password("admin", "s3cr3t") # Wait for job completion; returns final result is_locked = c.call("pool.dataset.lock", "tank/encrypted", job=True) print(f"Dataset locked: {is_locked}") # Unlock with passphrase args = {"datasets": [{"name": "tank/encrypted", "passphrase": "my-passphrase"}]} result = c.call("pool.dataset.unlock", "tank/encrypted", args, job=True) # Job with live progress callback def on_progress(job): pct = job["progress"]["percent"] desc = job["progress"]["description"] if pct is not None: print(f" [{pct:.0f}%] {desc}") result = c.call("pool.scrub", "tank", 0, job=True, callback=on_progress) # Get back a Job object for manual waiting job_obj = c.call("replication.run", 1, job="RETURN") print(f"Job started: {job_obj}") try: final = job_obj.result() # blocks until SUCCESS/FAILED/ABORTED print(f"Replication result: {final}") except ClientException as e: print(f"Job failed: {e}") ``` ``` -------------------------------- ### Raw API Key Format Source: https://github.com/truenas/api_client/blob/master/README.md The raw API key is a string combining the API key ID and the secret material, separated by a hyphen. This format can be used directly with the Python client or stored in a file. ```text 1-uz8DhKHFhRIUQIvjzabPYtpy5wf1DJ3ZBLlDgNVhRAFT7Y6pJGUlm0n3apwxWEU4 ``` -------------------------------- ### midclt CLI: Read Payload from Stdin Source: https://context7.com/truenas/api_client/llms.txt Pass API call arguments from standard input, which is useful for keeping sensitive data like API keys or passwords out of shell history. The hyphen `-` indicates that the payload should be read from stdin. ```bash cat /etc/truenas/new_user.json | midclt -U admin -K /etc/truenas/key.json call user.create - ```