Best Practices for GRAPE API Device Provisioning: Dynamic Setting UUIDs and settings_manager


When provisioning Gigaset devices via the GRAPE (Gigaset REST API for Endpoints) API, managing device-specific settings such as "Provisioning Server URL" can present challenges. Each setting is identified by a unique Universally Unique Identifier (UUID), and these UUIDs are not always static or universally applicable across all product models. This article outlines a robust, "dynamic" approach to reliably configure device settings, ensuring compatibility and flexibility.


The Challenge of Setting IDs (UUIDs)


In the GRAPE API, every configurable setting (e.g., ProvisioningServer, setting_server, certificate url) is associated with a specific UUID. This UUID is the key by which you target a setting when sending configuration updates.

There are two primary reasons why directly hardcoding these UUIDs in your provisioning scripts is not recommended:

  1. Product-Specific UUIDs: A setting's UUID can vary between different product families or even specific product models (e.g., the UUID for a "Provisioning Server" setting might be different for a gigasetP series device versus a gigasetN series device).

  2. Potential for Change: While not a frequent occurrence, UUIDs for settings could theoretically change over time with API updates or internal restructuring. A dynamic lookup ensures your integration remains functional without manual updates to hardcoded values.

To overcome these challenges, a dynamic discovery method is essential.


The "Dynamic" Approach: Retrieving Setting UUIDs


The recommended approach involves querying the GRAPE API to discover the correct UUID for a given setting parameter name and product code at runtime. This process ensures that your script always uses the correct, compatible UUID for the device it's provisioning.

The core logic for this dynamic discovery can be encapsulated in a function like find_setting_uuid:


def find_setting_uuid(param_name: str, product_code: str) -> str:
    hawk_auth = HawkAuth(id=API_KEY, key=API_SECRET, always_hash_content=False)
    headers = {'accept': 'application/json'}

    # Fetch API root and get link to product & setting lists
    response = requests.get(url=GRAPE_BASE_URL, headers=headers, auth=hawk_auth)
    if response.status_code != 200:
        print(f'Error fetching API root: {response.text}')
        exit(1)

    api_root = response.json()

    setting_list_url = f'{api_root["links"]["settings"]}'

    response = requests.get(url=setting_list_url, headers=headers, auth=hawk_auth)

    if response.status_code != 200:
        print(f'Error fetching setting list: {response.text}')
        exit(1)

    settings = response.json()

    product_url = f'{api_root["links"]["products"]}{product_code}'
    response = requests.get(url=product_url, headers=headers, auth=hawk_auth)

    if response.status_code != 200:
        print(f'Error fetching product: {response.text}')
        exit(1)

    product_detail = response.json()

    for setting in settings:
        if setting['param_name'] != param_name:
            continue
        # Crucial check: ensure the setting's product groups intersect with the product's groups
        if 'product_groups' in setting and 'groups' in product_detail:
            if bool(set(setting['product_groups']).intersection(product_detail['groups'])):
                return setting['uuid']
        # Fallback for settings that might not explicitly list product_groups or have universal applicability
        elif 'product_groups' not in setting and 'groups' not in product_detail:
             return setting['uuid']

    # If no matching UUID is found through the dynamic lookup
    return None


Explanation of the find_setting_uuid Logic:

  1. API Root Discovery: The function first queries the GRAPE API root (GRAPE_BASE_URL) to dynamically discover the URLs for the settings list and product details. This adds an extra layer of robustness.

  2. Retrieve All Settings: It then fetches the comprehensive list of all available settings from the settings endpoint. This list contains details for each setting, including its param_name, uuid, and importantly, product_groups.

  3. Retrieve Product Details: Simultaneously, it fetches the specific details for the product_code you are targeting from the products/{product_code} endpoint. These details include the groups that the specific product belongs to.

  4. Match and Intersect: The function iterates through the retrieved settings. For each setting, it checks if its param_name matches the param_name you are looking for (e.g., "ProvisioningServer"). Critically, it then performs an intersection check between the product_groups associated with the setting and the groups that the target product_code belongs to. If there's an overlap (meaning the setting is compatible with the product), the corresponding uuid is returned.


Important Nuance: The ProvisioningServer Fallback


While the dynamic lookup is generally effective, an observed behavior for certain settings, particularly "ProvisioningServer" when used with gigasetN and gigasetB product series, is that the direct product group intersection might not always yield a UUID even when the setting is known to be applicable.

In such specific scenarios, a robust implementation might include a fallback mechanism. For instance, in our application, if the dynamic lookup for ProvisioningServer on gigasetN or gigasetB devices fails, we've implemented a fallback to a known working UUID (094c8a73a08644a1aaba837c33dfb528). This pragmatic approach ensures reliability while awaiting potential API alignments in the future. This fallback is handled in the application logic that calls find_setting_uuid, not within find_setting_uuid itself, to keep the lookup function generic.


Applying Multiple Settings with settings_manager


The GRAPE API's PUT endpoint for devices (/companies/{company_id}/endpoints/{mac_address}) allows for powerful and flexible device configuration using the settings_manager field in the request payload.

The settings_manager is a JSON object (dictionary) where each key is a setting's UUID, and its value is another object containing the value to be set and any attrs (attributes) like perm (permissions, e.g., "RW" for read/write). This design enables you to send multiple setting updates in a single API call, streamlining your provisioning process.

Here's an example of how settings_manager is used in a PUT request payload:


def add_device_with_settings(
        mac_address: str,
        setting_server_uuid: str,
        setting_server_value: str,
        certificate_url_uuid: str,
        certificate_url_value: str) -> None:
    hawk_auth = HawkAuth(id=API_KEY, key=API_SECRET, always_hash_content=False)
    headers = {'accept': 'application/json', 'content-type': 'application/json'}
    device_data = {
        "mac": mac_address,
        "autoprovisioning_enabled": True, # Example: device settings
        "settings_manager": {
            setting_server_uuid: { # First setting using its discovered UUID
                "value": setting_server_value,
                "attrs": {"perm": "RW"},
            },
            certificate_url_uuid: { # Second setting using its discovered UUID
                "value": certificate_url_value,
                "attrs": {"perm": "RW"},
            }
        },
        "product": PRODUCT_CODE, # Product code is also part of the device data
    }
    # ... (rest of the API call to PUT device_data)

In this example, both setting_server_uuid and certificate_url_uuid are used as keys within the settings_manager object, allowing their respective values to be updated simultaneously for the specified device.


Currently Supported Product Codes


This dynamic provisioning approach has been successfully tested and confirmed to work with the following Gigaset product codes:


Conclusion


By implementing dynamic UUID discovery and leveraging the settings_manager object, your GRAPE API provisioning scripts can become significantly more robust, flexible, and future-proof. This approach minimizes reliance on hardcoded values, adapts to potential API variations across product lines, and allows for efficient configuration of multiple settings in a single API transaction.