HomeGuidesAPI ReferenceGuidesMRT APIConfiguration API
GitHubAirheads Developer Community
Guides

MSP Automation

📘

Version Note

This guide applies to PyCentral v2.0a19 and above. If you are on an earlier version, see Upgrading from PyCentral v1 to v2.

PyCentral's MSPBase class is built for Managed Service Provider (MSP) mode in HPE GreenLake Platform (GLP) & Central. It handles the access token lifecycle by generating MSP tokens, exchanging them for tenant-scoped tokens, refreshing on expiry, and caching tenant sessions. This ensures that your script can focus on the actual workflow across customer workspaces.

Features

  • Single credential — one set of API credentials drive the entire workflow, at both MSP and tenant level
  • MSP-level API access — make Central and GLP calls directly from the MSP workspace
  • Automatic token exchange — step into any tenant scope without writing the exchange flow yourself
  • Tenant-level API access — make Central and GLP calls scoped to a specific tenant
  • Per-tenant connection caching — each tenant connection is cached and reused, avoiding repeated token exchanges
  • Automatic token refresh — expired MSP and tenant tokens are renewed and retried transparently

Prerequisites

Credentials

MSPBase requires the unified credential format. You need to provide GLP API credentials from the MSP workspace. Please see Token Exchange guide for more details.

unified:
  client_id: <your-glp-client-id>
  client_secret: <your-glp-client-secret>
  workspace_id: <your-msp-workspace-id>
  base_url: <your-central-api-base-url>
  # cluster_name: <your-cluster-name>  # alternative to base_url

To learn more about unified credentials, please see Authentication.


How MSPBase Works

MSPBase extends NewCentralBase, PyCentral's connection class for New Central and GLP APIs, adding MSP-specific token exchange and tenant management on top. When you instantiate it, the SDK generates an MSP-scoped token from your credentials. From that point, it manages the full token lifecycle automatically.

📘

Note

  • Use MSPBase as a context manager with the with statement. This ensures the MSP and all tenant HTTP clients are closed cleanly when your script exits, regardless of whether it completes successfully or raises an exception.
  • The same command() pattern works for both MSP-level and tenant-scoped operations. The main difference is whether you call it on the MSPBase instance or on a connection returned by get_tenant_connection().

MSP-Level Operations

Use the MSPBase instance directly for operations in the MSP workspace like listing tenants, managing MSP-level inventory, or calling GLP & Central APIs.

from pycentral import MSPBase

with MSPBase(token_info="token.yaml", log_level="ERROR") as msp_conn:
    print("Successfully authenticated with MSPBase. Now making API calls...")
    
    print("\nListing tenants from Central API:")
    # List tenants from Central
    central_response = msp_conn.command(
        api_method="GET",
        api_path="network-msp/v1/list-tenants",
        api_params={"limit": 100, "next": 1},
    )

    if central_response["code"] == 200:
        for tenant in central_response["msg"].get("items", []):
            print(f'Central Tenant ID: {tenant.get("tenantId")}, Tenant Name: {tenant.get("tenantName")}')
    print()
    print("\nListing devices from GLP API:")
    # List devices from GLP
    glp_response = msp_conn.command(
        api_method="GET",
        api_path="devices/v1/devices",
        app_name="glp",  # routes the request to GLP instead of Central
    )

    if glp_response["code"] == 200:
        for device in glp_response["msg"].get("items", []):
            print(f'GLP Device ID: {device.get("id")}, Device Serial Number: {device.get("serialNumber")}')

Tenant-Scoped Operations

To act inside a tenant workspace, call get_tenant_connection(). This performs the MSP-to-tenant token exchange and returns a connection object that behaves like NewCentralBase.

You can identify the tenant by either:

  • tenant_workspace_id — GLP Workspace ID of Tenant
  • tenant_name — Name of Tenant
📘

Which tenant ID works

tenant_workspace_id is the id returned by the GLP workspaces/v1/msp-tenants API (the tenant's workspace ID). It is not the Central tenantId returned by network-msp/v1/list-tenants.

Tenant-scoped Central API call

from pycentral import MSPBase

with MSPBase(token_info="token.yaml", log_level="ERROR") as msp_conn:
    # Update tenant_name to match an actual tenant in your account
    tenant_conn = msp_conn.get_tenant_connection(tenant_name="Example Tenant Name")

    # Fetch devices in tenant's Central instance
    response = tenant_conn.command(
        api_method="GET",
        api_path="network-monitoring/v1/device-inventory",
    )
    print("Fetching devices in tenant...")
    if response["code"] == 200:
        for device in response["msg"].get("items", []):
            print(f'Serial Number: {device.get("serialNumber")}, Name: {device.get("deviceName")}')

Tenant-scoped GLP API call

from pycentral import MSPBase

with MSPBase(token_info="token.yaml", log_level="ERROR") as msp_conn:
    # Update tenant_name to match an actual tenant in your account
    tenant_conn = msp_conn.get_tenant_connection(tenant_name="Example Tenant Name") 

    glp_response = tenant_conn.command(
        api_method="GET",
        api_path="audit-log/v1/logs",
        app_name="glp",  # routes the request to GLP instead of Central
    )

    if glp_response["code"] == 200:
        print(f"Successfully retrieved audit logs from GLP API. Total logs: {len(glp_response['msg'].get('items', []))}")
        for log in glp_response["msg"].get("items", []):
            print(f'Log ID: {log.get("id")}, Description: {log.get("description")}, Category: {log.get("category")}')

Token Lifecycle

What happens during tenant token exchange

When you call get_tenant_connection(), the SDK runs through the same steps as the manual token exchange flow:

  1. Start with the current MSP token.
  2. Resolve the tenant workspace ID if only a name was provided.
  3. Normalize the workspace ID for the exchange endpoint.
  4. Exchange the MSP token for a tenant-scoped token.
  5. Return a connection object scoped to that tenant.

For the detailed sequence, see the MSP token exchange guide.

Automatic token renewal

The SDK renews tokens automatically. You do not need to build expiry checks around your API calls.

ScenarioSDK behavior
MSP token expires during an MSP API callGenerates a fresh MSP token and retries
MSP token expires during tenant token exchangeRefreshes the MSP token and retries the exchange
Tenant token expires during a tenant API callRefreshes the MSP token, re-exchanges for a new tenant token, and retries

Tenant connection caching

MSPBase caches tenant connections by tenant_workspace_id. The first call to a tenant performs the full exchange. Later calls for the same tenant reuse the cached connection, avoiding unnecessary round trips.

This is especially useful in loops that revisit tenants for monitoring, inventory checks, or staged configuration tasks.


Example: Iterate Across All Tenants

The pattern below covers the most common MSP automation scenario like fetching the tenant list once, then run a tenant-scoped operation for each.

from pycentral import MSPBase
from pycentral.new_monitoring import MonitoringDevices

with MSPBase(token_info="token.yaml", log_level="ERROR") as msp_conn:
    tenants_response = msp_conn.command(
        api_method="GET",
        api_path="workspaces/v1/msp-tenants",
        app_name="glp",
    )
    if tenants_response["code"] != 200:
        raise RuntimeError(tenants_response["msg"])

    for tenant in tenants_response["msg"].get("items", []):
        try:
            tenant_conn = msp_conn.get_tenant_connection(
                tenant_workspace_id=tenant.get("id")
            )
            print("Connected to tenant:", tenant.get("workspaceName"))
            tenant_devices = MonitoringDevices.get_all_device_inventory(central_conn=tenant_conn)
            if tenant_devices:
                for device in tenant_devices:
                    print(f'Device Serial Number: {device.get("serialNumber")}, Device Status: {device.get("status")}')
            else:
                print("No devices found for this tenant.")
        except Exception as e:
            # One failing tenant should not stop the loop
            print(f'Skipping tenant {tenant.get("workspaceName")}: {e}')
        print()

The patterns in this guide cover the most common scenarios: MSP-level operations in Central & GLP; tenant-scoped operations in Central & GLP, & iterating across all tenants in MSP workspace. From here, the MSP module documentation is the next stop for the full method signatures and any parameters not covered by the example above.

🛠️

Troubleshooting

  • Token exchange fails with 403 (LoginError): the API client must belong to the MSP workspace, and the tenant must be a tenant of that MSP workspace.
  • No tenant found with name '...': tenant_name must match the tenant's workspaceName exactly. Use tenant_workspace_id if names are ambiguous (Multiple tenants found).
  • Tenant Central calls fail: make sure base_url or cluster_name is set under unified.

What’s Next

Learn more about the MSPBase and TenantBase modules

Did this page help you?