Provide Customer-Specific File Workspaces

Provide controlled customer file workspaces with access policies, file use, transfer execution, and workspace-level separation.

TeamsIT EngineersDevelopers

Getting Started#

Basic Concept#

Manage File Transfers Separately by Workspace

A file workspace is a way to organize the files, storage locations, transfer targets, and access scope used for a specific task into a single space.

For example, files used by development, content, and data analysis teams can be organized into separate workspaces, with the required storage locations and transfer devices connected to each workspace.

File Workspace
Files
Transfer
Storage
Targets
Access
Users / Devices

This configuration lets you separate and manage files and transfer environments by workspace across multiple business tasks.

Workspace Flow#

Continue from File Storage to Transfer and Result Verification

Users select a workspace with configured access permissions and store or check the required files.

When a transfer job connected to the workspace is run, the selected files are transferred to the specified devices and business environments, and the execution result can also be checked in the same workspace.

  1. Select Workspace
  2. Store, Check Files
  3. Run Transfer Job
  4. Reflect on Target Device
  5. Check Processing Result

Separation Benefits#

Manage Files from Multiple Tasks and Devices by Purpose

Organizing workspaces by task lets you manage files, storage locations, transfer targets, and access scope under a single management standard.

Category Individual Management Workspace Management
Files Check files in multiple locations individually Manage task-specific files by workspace
Storage Location Check the location for each file Configure storage locations by workspace
Transfer Target Check the target for each job Connect transfer devices and paths by workspace
Access Scope Check files by user Manage access scope by workspace
Result Verification Check results by job Check execution results by workspace

Business Team#

Manage and Use Required Files in Workspaces

Workspace Selection#

Check Task-Specific Files and Work in One Place

When starting a task, select the workspace containing the required files and jobs.

A workspace contains the files used for the task together with connected transfer jobs, allowing the responsible user to perform file operations in the workspace required for the current task.

  • My Workspaces
  • Project A
  • Files
  • Transfers
  • Media Team
  • Files
  • Transfers
  • Data Analysis
  • Files
  • Transfers

File Usage#

Store Required Files and Use Them in the Next Task

Store the files required for the task in the workspace and check the files to use for the current work.

Stored files can be used with the business flow connected to the workspace, allowing the responsible user to select the required files and continue to the next task.

Workspace
Upload Files
Browse Files
Select Files
Next Work

Run Transfer#

Send Files Using a Prepared Transfer Job

After selecting the required files, run the transfer job configured in the workspace.

The responsible user can select the files to use for the current task and the job to run, then check progress and processing results.

Select Files
Select Transfer
Run
Progress
Target
Result

Business teams can manage required files in workspaces and use prepared transfer flows to continue to the next task.

IT Engineer#

Configure File Workspaces and Transfer Environments and Manage Them Centrally

Workspace Setup#

Configure File Storage Locations and Management Scope by Task

Create workspaces according to business purposes and configure the file storage locations and management scope used by each workspace.

For example, development outputs, media files, and data processing results can each be organized into separate workspaces.

Organization
Dev
Storage
Media
Storage
Data
Storage

Connect Workspaces, Target Devices, and File Paths

Connect the transfer target devices and file paths used by each workspace.

Connect servers, storage, and business devices to workspaces and configure file transfer paths to build task-specific transfer environments.

Workspace

  • Source Storage
  • Transfer Flow
  • Target DevicesServerStorageSystem

Access Policy#

Manage Access Scope and Transfer Rules by User and Device

For each workspace, configure user and group access scope, connectable devices, file paths, and transfer jobs that can be run.

When the business environment or user configuration changes, adjust access scope and processing rules from the same policy screen.

Workspace Policy
Users
Access
Groups
Operations
Transfer Rules
Devices
Paths

Management Item Configuration
Workspace Workspaces by user and task
Users / Groups Users and groups that use the workspace
Devices Connectable devices
Paths Paths used for files
Operations File operations that can be run
Transfer Rules File transfer criteria and processing rules

Operations Management#

Check Transfer Results by Workspace and Overall Operational Status

Check file transfers and processing results for each workspace and centrally manage the status of multiple workspaces and connected devices.

Operators can check the execution status of a specific workspace or understand the operational situation based on job status across the entire environment.

Central Management
Workspace
Devices
Runs
Operations View

Workspaces can be configured and managed through the following flow.

Configure Workspace
Connect Storage Location
Set Target Devices, Transfer Paths
Configure User, Device Access Policies
Run Transfer Job
Check Workspace-Specific, Overall Operational Status

Developer#

Include the Workspace Identifier and Token in Requests and Handle Permission Responses

Authentication Handling#

Issue Tokens and Refresh Them When They Expire

The workspace API uses an access token for authentication. If no token is available, log in with the account to obtain one.

import os
import threading

import requests

BASE_URL = os.getenv("INNORIX_BASE_URL", "https://app.innorix.com").rstrip("/")


def login(email, password):
    response = requests.post(f"{BASE_URL}/api/auth/login",
                             json={"email": email, "password": password},
                             timeout=30)
    response.raise_for_status()

    # the login response nests the tokens under data.user
    user = response.json()["data"]["user"]
    return user["accessToken"], user.get("refreshToken")


def refresh(refresh_token):
    response = requests.post(f"{BASE_URL}/api/auth/token/refresh",
                             headers={"X-Refresh-Token": refresh_token},
                             timeout=30)
    response.raise_for_status()

    # the refresh response nests the tokens directly under data
    data = response.json()["data"]
    return data["accessToken"], data.get("refreshToken")

The response structures for login and refresh are different. Login nests the tokens under data.user, while refresh places them directly under data.

A refresh token is valid only once. If a server application uses the same token across multiple threads, lock the refresh operation so it cannot occur concurrently.

class Session:
    def __init__(self, email, password):
        self._lock = threading.Lock()
        self._access, self._refresh = login(email, password)

    @property
    def access_token(self):
        with self._lock:
            return self._access

    def renew(self):
        with self._lock:
            self._access, self._refresh = refresh(self._refresh)
            return self._access

Workspace Assignment#

Send the Workspace Identifier with Every Request

The workspace is passed in a header rather than the request body. Because specifying it at each call site can lead to omissions, centralize request construction in one place.

class Client:
    def __init__(self, session, workspace_id=None):
        self.session = session
        self.workspace_id = workspace_id

    def request(self, method, path, body=None, params=None, retried=False):
        headers = {
            "Content-Type": "application/json",
            "Authorization": f"Bearer {self.session.access_token}",
        }

        # when omitted the account's current workspace is used
        if self.workspace_id:
            headers["x-workspace-id"] = self.workspace_id

        response = requests.request(method, BASE_URL + path, headers=headers,
                                    json=body, params=params, timeout=30)

        if response.status_code == 401 and not retried:
            self.session.renew()
            return self.request(method, path, body, params, retried=True)

        return response

If no workspace identifier is specified, the account's currently accessible workspace is used. An application that uses a single workspace can operate without specifying one.

Token expiration is also handled at this point. When a 401 is received, refresh once and retry; if the retried request fails again, propagate the failure as-is.

Check Access Scope#

Check the Account's View Scope and Accessible Devices

me = api("GET", "/api/auth/me") or {}
print(me)

result = api("GET", "/api/devices", params={"page": 1, "size": 20}) or {}

if result.get("viewScope"):
    print("view scope:", result["viewScope"])

for device in result.get("devices") or []:
    print(device["deviceId"], device["name"])
Response Item Details
devices List of devices connected to the workspace
totalRows, lastPage Total count and last page
viewScope Current account view scope

The device list is returned as the data.devices array. Use viewScope in the response to determine whether the account can view everything or only part of the environment.

Permission Handling#

Distinguish 401 and 403 and Handle Them with Re-login or UI Guidance

Authentication failure and insufficient permissions must be handled differently. The former requires re-login, while the latter requires UI guidance.

Status Meaning Handling
401 Token is missing or invalid Refresh and retry; if it fails, re-login
403 Cannot access the workspace or path Inform the user
404 Device or path does not exist Inform the user that the target is no longer available
429 Too many requests Retry after a short delay
class WorkspaceForbidden(Exception):
    pass


def call(client, method, path, body=None, params=None):
    response = client.request(method, path, body, params)

    if response.status_code == 403:
        raise WorkspaceForbidden(path)

    payload = response.json() if response.content else {}

    if not response.ok:
        raise RuntimeError(payload.get("message") or f"HTTP {response.status_code}")

    return payload.get("data")


try:
    devices = call(client, "GET", "/api/devices")
except WorkspaceForbidden:
    devices, notice = [], "no permission to access this workspace"

Separating insufficient permissions into a dedicated exception lets the calling code convert them into a user-facing message. Because results can differ by workspace even for the same user, display the current workspace at the top of the UI so users do not mistake an empty list for a permission problem.

Multi-Workspace Handling#

Separate Credentials in Code That Handles Multiple Workspaces

Passing the workspace identifier as a function argument can lead to omissions at some call site. Create a client for each workspace and pass the client object instead.

clients = {
    workspace_id: Client(session, workspace_id)
    for workspace_id in WORKSPACE_IDS
}


def collect_devices():
    result = {}

    for workspace_id, client in clients.items():
        try:
            result[workspace_id] = call(client, "GET", "/api/devices")
        except WorkspaceForbidden:
            continue

    return result

There is no endpoint for listing workspaces. Provide candidate workspaces and filter them to determine which are accessible.

If each customer has a separate account, separate the sessions as well. Keep their creation points separate so they are not mixed with a configuration where multiple workspaces share one session.

def build_client(config):
    session = Session(config["email"], config["password"])
    return Client(session, config["workspace_id"])


clients = {name: build_client(cfg) for name, cfg in TENANTS.items()}

A batch that iterates over multiple workspaces should not let a failure in one workspace stop the entire batch.

failed = []

for name, client in clients.items():
    try:
        run_for(client)
    except Exception as error:
        failed.append((name, error))

for name, error in failed:
    print(f"{name}: {error}")
Management Item Handled in Code
Session Access and refresh tokens by account
Workspace Workspace identifier in the request header
Permissions 403 response and UI guidance
Resources Devices and view scope by workspace
Execution Transfers created within the workspace