# Add a license (disconnected)


> Download a JWT license from MyF5 and apply it to F5 NGINX Instance Manager in a disconnected (offline) environment.


## Overview

In a disconnected environment, systems don't have internet access. You'll download and apply your JSON Web Token (JWT) license to F5 NGINX Instance Manager, then verify your entitlements with F5.

**Note:** Starting with version 2.22, NGINX Instance Manager no longer requires a JWT license. All features are available immediately after installation. If you're running version 2.21 or earlier, follow the steps on this page.

**Note:** Use tools such as `curl` or [Postman](https://www.postman.com) to send requests to the NGINX Instance Manager REST API.
The API base URL is `https://<NIM-FQDN>/api/[nim|platform]/<API_VERSION>`.  
All requests require authentication. For details on authentication methods, see the [API overview](/nim/fundamentals/api-overview.md).

## Before you begin

### Set the operation mode to disconnected

To set up NGINX Instance Manager for a disconnected environment, set `mode_of_operation` to `disconnected` in the configuration file.

1. Open the `/etc/nms/nms.conf` file and add the following in the `integrations:license` section:

    ``` yaml
    integrations:
        license:
            mode_of_operation: disconnected
    ```

2.	Restart NGINX Instance Manager:

    ```shell
    sudo systemctl restart nms
    ```

### Download the JWT license from MyF5 {#download-license}

1. Log in to [MyF5](https://my.f5.com/manage/s/).
1. Go to **My Products & Plans > Subscriptions** to see your active subscriptions.
1. Find your NGINX subscription, and select the **Subscription ID** for details.
1. Download the **JSON Web Token** file from the subscription page.

## Add license and submit initial usage report {#add-license-submit-initial-usage-report}

#### Bash script (recommended)

### Add license and submit initial usage report with a bash script

To add a license and submit the initial usage report in a disconnected environment, use the `license_usage_offline.sh` script. Run this script on a system that can connect to NGINX Instance Manager and to `https://product.apis.f5.com/` on port `443`. Replace each placeholder with your specific values.

**Note:**  The script won't work if you've already added a license. 

1. [icon: download] [Download license_usage_offline.sh](/scripts/license_usage_offline.sh).
1.	Run the following command to allow the script to run:

    ```shell
    chmod +x <PATH_TO_SCRIPT>/license_usage_offline.sh
    ```

1. Run the script. Replace each placeholder with your specific values:

    ```shell
    ./license_usage_offline.sh \
      -j <LICENSE_FILENAME>.jwt \
      -i <NIM_IP_ADDRESS> \
      -u admin \
      -p <PASSWORD> \
      -s initial
    ```

    This command adds the license, downloads the initial usage report (`report.zip`), submits the report to F5 for acknowledgment, and uploads the acknowledgment back to NGINX Instance Manager.

#### Full license_usage_offline.sh script

```shell
#!/bin/bash

# Enable strict mode for better error handling
set -euo pipefail
IFS=$'\n\t'

# Debug mode
if [[ "${DEBUG:-false}" == "true" ]]; then
  set -x  # Enable command tracing
  echo "Debug mode enabled"
  echo "Running in directory: $(pwd)"
  echo "Script arguments: $*"
  env | grep -E 'JWT_FILE|NIM_IP|USERNAME|PASSWORD|USE_CASE'
fi

# Set timeouts for operations
CURL_TIMEOUT=${CURL_TIMEOUT:-30}
API_POLL_TIMEOUT=${API_POLL_TIMEOUT:-60}

# Function to log with timestamp
log() {
  echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"
}

log "Script started"

# Function to display usage
usage() {
  echo "Usage: $0 -j <JWT file> -i <NIM IP> -u <username> -p <password> -s <initial|telemetry>"
  echo
  echo "Options:"
  echo "  -j <JWT file>     Path to the JWT (JSON Web Token) file used for authentication."
  echo "  -i <NIM IP>       IP address of the NIM (NGINX Instance Manager) to connect to."
  echo "  -u <username>     Username for login/authentication to NIM (NGINX Instance Manager)."
  echo "  -p <password>     Password corresponding to the username for NIM (NGINX Instance Manager) authentication."
  echo "  -s <mode>         Script execution mode. One of the following:"
  echo "                      initial       - Perform Initial License Activation"
  echo "                      telemetry     - Perform telemetry submission: download usage report from NGINX Instance Manager and submit to F5."
  exit 1
}

# Parse command-line arguments
while getopts ":j:i:u:p:s:" opt; do
  case $opt in
    j) JWT_FILE="$OPTARG" ;;
    i) NIM_IP="$OPTARG" ;;
    u) USERNAME="$OPTARG" ;;
    p) PASSWORD="$OPTARG" ;;
    s) USE_CASE="$OPTARG" ;;
    *) usage ;;
  esac
done

# Check if all required arguments are provided
if [ -z "${JWT_FILE:-}" ] || [ -z "${NIM_IP:-}" ] || [ -z "${USERNAME:-}" ] || [ -z "${PASSWORD:-}" ] || [ -z "${USE_CASE:-}" ]; then
  usage
fi

echo "Running $USE_CASE report"

# Ensure /tmp directory exists or else create it and proceed
if [ ! -d "/tmp" ]; then
  echo "/tmp directory does not exist. Creating it now..."
  mkdir -p /tmp || { echo "Failed to create /tmp directory. Exiting."; exit 1; }
fi

# Read JWT contents
if [ ! -f "$JWT_FILE" ]; then
  echo -e "JWT file '$JWT_FILE' not found.$" >&2
  exit 1
fi
JWT_CONTENT=$(<"$JWT_FILE")

# Encode credentials
AUTH_HEADER=$(echo -n "$USERNAME:$PASSWORD" | base64)

# Check connectivity to NGINX Instance Manager IP and the F5 licensing server (product.apis.f5.com)
echo -e "Checking connectivity to NGINX Instance Manager and F5 licensing server..."

# Function to test ping
check_ping() {
  local host=$1
  echo "Pinging $host... "
  if ! ping -c 2 -W 2 "$host" > /dev/null 2>&1; then
    echo -e "Cannot reach $host. Please check your network, DNS or check if any proxy is set.$" >&2
    exit 1
  fi
  echo -e "$host is reachable."
}

# Call the function for each host
is_ipv4() {
  local ip=$1
  if [[ $ip =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then
    IFS='.' read -r -a octets <<< "$ip"
    for octet in "${octets[@]}"; do
      if ((octet < 0 || octet > 255)); then
        return 1
      fi
    done
    return 0
  else
    return 1
  fi
}
echo "Checking connectivity to NGINX Instance Manager using Curl ..."
if ! curl -sk --output /dev/null --silent --fail --max-time $CURL_TIMEOUT "https://$NIM_IP"; then
  echo -e "The NGINX Instance Manager UI is not reachable on $NIM_IP"
  exit 1
fi 
echo "Checking connectivity to F5 licensing server..."
SERVER_RESPONSE=$(curl -v --max-time $CURL_TIMEOUT https://product.apis.f5.com 2>&1)

# Check if the server is reachable by verifying connection was established
if echo "$SERVER_RESPONSE" | grep -q "Connected to product.apis.f5.com" && echo "$SERVER_RESPONSE" | grep -q "server accepted"; then
  echo -e "The licensing server is reachable on product.apis.f5.com"
else
  echo -e "The licensing server is not reachable on product.apis.f5.com"
  echo -e "Connection details: $SERVER_RESPONSE"
  exit 1
fi 

# NGINX Instance Manager Version check 
VERSION_JSON=$(curl -sk -X GET "https://$NIM_IP/api/platform/v1/modules/versions" \
  --header "Content-Type: application/json" \
  --header "Authorization: Basic $AUTH_HEADER")
NIM_VER=$(echo "$VERSION_JSON" | sed -E 's/.*"nim"[ \t]*:[ \t]*"([0-9]+\.[0-9]+)(\.[0-9]+)?".*/\1/')
echo "Current version of NGINX Instance Manager is $NIM_VER"

# Construct JSON payload
JSON_PAYLOAD=$(cat <<EOF
  {
    "metadata": {
      "name": "license"
    },
    "desiredState": {
      "content": "$JWT_CONTENT"
    }
  }
EOF
)

# Send GET request and capture response and status code
response=$(curl -sk -w "%{http_code}" -o /tmp/device_mode.json "https://$NIM_IP/api/platform/v1/report/device_mode" -H 'accept: application/json' -H "Authorization: Basic $AUTH_HEADER")

# Extract status code and response body
http_code="${response: -3}"
body=$(cat /tmp/device_mode.json)

# Check response code
if [[ "$http_code" != "200" ]]; then
  echo "Request failed with status code $http_code"
  exit 1
fi

# Parse device_mode using jq
device_mode=$(echo "$body" | jq -r '.device_mode')

# Use the value
echo "Device mode is: $device_mode"

# Check value and act
if [[ "$device_mode" == "CONNECTED" ]]; then
  echo "Device mode is CONNECTED. This script is only for DISCONNECTED mode"
  exit 1
fi

if [[ "$USE_CASE" == "initial" ]]; then
  echo "Applying JWT license"
  sleep 5  
  RESPONSE=$(curl -sS -k --max-time 10 -w "\n%{http_code}" -X POST "https://$NIM_IP/api/platform/v1/license?telemetry=true" \
    -H "Content-Type: application/json" \
    -H "Authorization: Basic $AUTH_HEADER" \
    -d "$JSON_PAYLOAD")
  echo "Uploaded License"
  HTTP_BODY=$(echo "$RESPONSE" | sed '$d')
  HTTP_STATUS=$(echo "$RESPONSE" | tail -n1)
  if [ "$HTTP_STATUS" -ne 202 ]; then
    echo -e "HTTP request failed with status code $HTTP_STATUS.\nResponse: $HTTP_BODY$" >&2
    if echo "$HTTP_BODY" | jq -r '.message' | grep -q "failed to register token. already registered"; then
      echo -e "NGINX Instance Manager already registered and licensed.\nIf needed, terminate the current license manually in the NGINX Instance Manager UI and re-run the script with the correct license.\nhttps://docs.nginx.com/nginx-instance-manager/disconnected/add-license-disconnected-deployment/"
    fi
    exit 1
  fi
fi

if [[ "$NIM_VER" < "2.18" ]]; then
  echo "NGINX Instance Manager version $NIM_VER is not supported by this script. Please use NGINX Instance Manager 2.18 or later"
  exit 1
elif [[ "$NIM_VER" == "2.18" ]] || [[ "$NIM_VER" == "2.19" ]]; then
  echo "NGINX Instance Manager version $NIM_VER detected."

  # Send the PUT request and separate body and status code
  PUT_RESPONSE_CODE=$(curl -k -s -w "%{http_code}" -o /tmp/put_response.json --location --request PUT "https://$NIM_IP/api/platform/v1/license?telemetry=true" \
    --header "Content-Type: application/json" \
    --header "Authorization: Basic $AUTH_HEADER" \
    --data '{
      "desiredState": {
          "content": "'"$JWT_CONTENT"'",
          "type": "JWT",
          "features": [
              {"limit": 0, "name": "NGINX_NAP_DOS", "valueType": ""},
              {"limit": 0, "name": "IM_INSTANCES", "valueType": ""},
              {"limit": 0, "name": "TM_INSTANCES", "valueType": ""},
              {"limit": 0, "name": "DATA_PER_HOUR_GB", "valueType": ""},
              {"limit": 0, "name": "NGINX_INSTANCES", "valueType": ""},
              {"limit": 0, "name": "NGINX_NAP", "valueType": ""},
              {"limit": 0, "name": "SUCCESSFUL_API_CALLS_MILLIONS", "valueType": ""},
              {"limit": 0, "name": "IC_PODS", "valueType": ""},
              {"limit": 0, "name": "IC_K8S_NODES", "valueType": ""}
          ]
      },
      "metadata": {
          "name": "license"
      }
    }')

  echo "Response status code: $PUT_RESPONSE_CODE"

  if [[ "$PUT_RESPONSE_CODE" == "200" ]]; then
    echo -e "(legacy): License applied successfully in DISCONNECTED mode for version $NIM_VER."
  else
    echo -e "(legacy): License PUT request failed. Status code: $PUT_RESPONSE_CODE$"
    echo "clear the license database and re-trigger the script again"
    exit 1
  fi
fi

if [[ "$USE_CASE" != "telemetry" ]]; then
  RESPONSE=$(curl -sS -k --max-time 10 -w "\n%{http_code}" -X POST "https://$NIM_IP/api/platform/v1/license?telemetry=true" \
    -H "Content-Type: application/json" \
    -H "Authorization: Basic $AUTH_HEADER" \
    -d "$JSON_PAYLOAD")

  HTTP_BODY=$(echo "$RESPONSE" | sed '$d')
  HTTP_STATUS=$(echo "$RESPONSE" | tail -n1)

  echo -e "License applied successfully in DISCONNECTED mode."
fi

sleep 5
echo "Executing telemetry tasks"
if [[ "$NIM_VER" == "2.18" ]] || [[ "$NIM_VER" == "2.19" ]]; then
  if [[ "$USE_CASE" == "initial" ]]; then
    HTTP_RESPONSE=$(curl -k -sS -w "\n%{http_code}" --location "https://$NIM_IP/api/platform/v1/report/download?format=zip&reportType=initial" \
      --header "accept: application/json" \
      --header "authorization: Basic $AUTH_HEADER" \
      --header "content-type: application/json" \
      --output /tmp/response.zip)
  else
     prepare_usage_command="curl --insecure --location 'https://$NIM_IP/api/platform/v1/report/download?format=zip&reportType=telemetry&telemetryAction=prepare' \
          --header 'accept: application/json' \
          --header 'authorization: Basic $AUTH_HEADER'
    report_save_path="${output_file:-/tmp/response.zip}"

    download_usage_command="curl --insecure --location 'https://$NIM_IP/api/platform/v1/report/download?format=zip&reportType=telemetry&telemetryAction=download' \
      --header 'accept: */*' \
      --header 'authorization: Basic $AUTH_HEADER' \
      --output \"$report_save_path\""
    
    if [ "$USE_CASE" == "telemetry" ]; then
      echo "Running telemetry stage: "
      response=$(eval $prepare_usage_command)
      sleep 2
      if echo "$response" | grep -q '"telemetry":"Report generation in progress"'; then
        echo -e "Success: Report generation is in progress."
      else
        echo -e "Failure: Report generation not in progress or unexpected response."
        exit 1
      fi
      echo "Running command: $download_usage_command"
      eval $download_usage_command
    else
      echo "Running command: $download_usage_command"
      eval $download_usage_command
    fi
  fi
else
  HTTP_RESPONSE=$(curl -k -sS -w "\n%{http_code}" --location "https://$NIM_IP/api/platform/v1/report/download?format=zip" \
  --header "accept: application/json" \
  --header "authorization: Basic $AUTH_HEADER" \
  --header "content-type: application/json" \
  --output /tmp/response.zip)

  HTTP_STATUS=$(echo "$HTTP_RESPONSE" | tail -n1)
  if [ "$HTTP_STATUS" -ne 200 ]; then
    echo -e "Failed to download usage report from NGINX Instance Manager. HTTP Status Code: $HTTP_STATUS" >&2
    echo "Please verify that NGINX Instance Manager is reachable and the credentials are correct." >&2
    echo "(or) Verify that NGINX Instance Manager is licensed before using the 'telemetry' flag (run it with 'initial' first)."
    rm -f /tmp/response.zip
    exit 1
  fi
fi

echo -e "Usage report downloaded successfully as '/tmp/response.zip'."

echo "Uploading the usage report to F5 Licensing server"
TEEM_UPLOAD_URL="https://product.apis.f5.com/ee/v1/entitlements/telemetry/bulk"

UPLOAD_RESULT=$(curl -sS -w "\n%{http_code}" --location "$TEEM_UPLOAD_URL" \
  --header "Authorization: Bearer $JWT_CONTENT" \
  --form "file=@/tmp/response.zip")

UPLOAD_STATUS=$(echo "$UPLOAD_RESULT" | tail -n1)
UPLOAD_BODY=$(echo "$UPLOAD_RESULT" | sed '$d')

if [ "$UPLOAD_STATUS" -ne 202 ]; then
  echo -e "Usage report upload failed. HTTP Status: $UPLOAD_STATUS$" >&2
  echo "Response Body: $UPLOAD_BODY" >&2
  exit 1
fi

if ! echo "$UPLOAD_BODY" | jq empty >/dev/null 2>&1; then
  echo -e "Upload response is not valid JSON. Response: $UPLOAD_BODY$" >&2
  exit 1
fi

STATUS_LINK=$(echo "$UPLOAD_BODY" | jq -r '.statusLink // empty')
if [ -z "$STATUS_LINK" ]; then
  echo -e "Failed to extract statusLink from the upload response. Response: $UPLOAD_BODY$" >&2
  exit 1
fi

echo "StatusLink extracted: $STATUS_LINK"
STATUS_ID=$(echo "$STATUS_LINK" | sed 's|/ee/v1/entitlements/telemetry/bulk/status/||')

echo "Validating the report status"
echo "Validating report status using status ID: $STATUS_LINK"

STATUS_URL="https://product.apis.f5.com/ee/v1/entitlements/telemetry/bulk/status/$STATUS_ID"

sleep 5
STATUS_RESPONSE=$(curl -k -sS -w "\n%{http_code}" --location "$STATUS_URL" \
  --header "Authorization: Bearer $JWT_CONTENT")

STATUS_BODY=$(echo "$STATUS_RESPONSE" | sed '$d')
STATUS_CODE=$(echo "$STATUS_RESPONSE" | tail -n1)

if [ "$STATUS_CODE" -ne 200 ]; then
  echo -e "Status check failed. HTTP Status: $STATUS_CODE$" >&2
  echo "Response Body: $STATUS_BODY" >&2
  exit 1
fi

if ! echo "$STATUS_BODY" | jq empty >/dev/null 2>&1; then
  echo -e "Invalid JSON in status body: $STATUS_BODY$" >&2
  exit 1
fi

PERCENTAGE_COMPLETE=$(echo "$STATUS_BODY" | jq -r '.percentageComplete')
PERCENTAGE_SUCCESSFUL=$(echo "$STATUS_BODY" | jq -r '.percentageSuccessful')
READY_FOR_DOWNLOAD=$(echo "$STATUS_BODY" | jq -r '.readyForDownload')

TIME_LIMIT=30
START_TIME=$(date +%s)

elapsed_time() {
  echo $(($(date +%s) - $START_TIME))
}

while true; do
  if [ "$PERCENTAGE_COMPLETE" -eq "100" ] && [ "$READY_FOR_DOWNLOAD" == "true" ]; then
    echo -e "Validating Report."
    break
  fi

  if [ $(elapsed_time) -ge "$TIME_LIMIT" ]; then
    echo -e "Time limit exceeded. Report validation failed."
    echo "  percentageComplete: $PERCENTAGE_COMPLETE"
    echo "  percentageSuccessful: $PERCENTAGE_SUCCESSFUL"
    echo "  readyForDownload: $READY_FOR_DOWNLOAD"
    echo "  F5 upload issue failed even after 30 seconds"
    echo "  re-run the script"
    exit 1
  fi

  echo -e "Report validation failed. Waiting for conditions to be met...$"
  echo "  percentageComplete: $PERCENTAGE_COMPLETE"
  echo "  percentageSuccessful: $PERCENTAGE_SUCCESSFUL"
  echo "  readyForDownload: $READY_FOR_DOWNLOAD"

  sleep 5
done

echo -e "Report validated successfully. All conditions met."

echo "Downloading report from F5 License server..."
DOWNLOAD_URL="https://product.apis.f5.com/ee/v1/entitlements/telemetry/bulk/download/$STATUS_ID"
DOWNLOAD_RESPONSE=$(curl -sS -w "%{http_code}" --location "$DOWNLOAD_URL" \
  --header "Authorization: Bearer $JWT_CONTENT" \
  --output /tmp/response_teem.zip)

HTTP_STATUS=$(echo "$DOWNLOAD_RESPONSE" | tail -n1)
if [ "$HTTP_STATUS" -ne 200 ]; then
  echo -e "Failed to download the report from F5. HTTP Status Code: $HTTP_STATUS$" >&2
  exit 1
fi

echo -e "Report downloaded successfully from F5 as '/tmp/response_teem.zip'."

echo "Uploading the license acknowledgement to NGINX Instance Manager..."
UPLOAD_URL="https://$NIM_IP/api/platform/v1/report/upload"
UPLOAD_RESPONSE=$(curl -k -sS --location "$UPLOAD_URL" \
  --header "Authorization: Basic $AUTH_HEADER" \
  --form "file=@/tmp/response_teem.zip" \
  -w "%{http_code}" -o /tmp/temp_response.json)

HTTP_STATUS=$(echo "$UPLOAD_RESPONSE" | tail -n1)
UPLOAD_MESSAGE=$(cat /tmp/temp_response.json | jq -r '.message')

if ! [[ "$HTTP_STATUS" =~ ^[0-9]+$ ]]; then
  echo -e "Invalid HTTP status code. Response: $UPLOAD_RESPONSE$" >&2
  exit 1
fi

if [ "$UPLOAD_MESSAGE" != "Report uploaded successfully." ] || [ "$HTTP_STATUS" -ne 200 ]; then
  echo -e "Upload failed. Response: $UPLOAD_RESPONSE$" >&2
  exit 1
fi
echo -e "Acknowledgement uploaded successfully to NGINX Instance Manager."
```

#### REST

### Add license and submit initial usage report with curl

To license NGINX Instance Manager, complete each of the following steps in order.

**Note:**  This command won't work if you've already added a license. 

Run these `curl` commands on a system that can connect to NGINX Instance Manager and to `https://product.apis.f5.com/` on port `443`. Replace each placeholder with your specific values.

**Note:**  The -k flag skips SSL certificate validation. Use this only if your NGINX Instance Manager is using a self-signed certificate or if the certificate is not trusted by your system. 

1. **Add the license to NGINX Instance Manager**:

    ```shell
    curl -k --location 'https://<NIM_FQDN>/api/platform/v1/license?telemetry=true' \
    --header 'Content-Type: application/json' \
    --header 'Authorization: Basic <BASE64_ENCODED_CREDENTIALS>' \
    --data '{
      "metadata": {
        "name": "license"
      },
      "desiredState": {
        "content": "<JSON_WEB_TOKEN>"
      }
    }'
    ```

1. **Poll the license status on NGINX Instance Manager**:

   Use this command to check the current license status. Look for `INITIALIZE_ACTIVATION_COMPLETE` or `CONFIG_REPORT_READY` in the status field. Poll periodically if necessary.

    ```shell
    curl -k "https://<NIM_FQDN>/api/platform/v1/license" \
    --header "accept: application/json" \
    --header "authorization: Basic <BASE64_ENCODED_CREDENTIALS>"
    ```

1. **Update the license configuration on NGINX Instance Manager (not required in 2.20 or later)**:

   This fully applies the license configuration.

    ```shell
    curl -k --location --request PUT "https://<NIM_FQDN>/api/platform/v1/license?telemetry=true" \
    --header "Content-Type: application/json" \
    --header "Authorization: Basic <BASE64_ENCODED_CREDENTIALS>" \
    --data '{
      "desiredState": {
        "content": "",
        "type": "JWT",
        "features": [
          {"limit": 0, "name": "NGINX_NAP_DOS", "valueType": ""},
          {"limit": 0, "name": "IM_INSTANCES", "valueType": ""},
          {"limit": 0, "name": "TM_INSTANCES", "valueType": ""},
          {"limit": 0, "name": "DATA_PER_HOUR_GB", "valueType": ""},
          {"limit": 0, "name": "NGINX_INSTANCES", "valueType": ""},
          {"limit": 0, "name": "NGINX_NAP", "valueType": ""},
          {"limit": 0, "name": "SUCCESSFUL_API_CALLS_MILLIONS", "valueType": ""},
          {"limit": 0, "name": "IC_PODS", "valueType": ""},
          {"limit": 0, "name": "IC_K8S_NODES", "valueType": ""}
        ]
      },
      "metadata": {
        "name": "license"
      }
    }'
    ```

1. **Download the initial usage report**:

    ```shell
    curl -k --location 'https://<NIM_FQDN>/api/platform/v1/report/download?format=zip&reportType=initial' \
    --header 'accept: */*' \
    --header 'Authorization: Basic <BASE64_ENCODED_CREDENTIALS>' \
    --output report.zip
    ```

1. **Submit the usage report to F5 for verification**:

    ```shell
    curl --location 'https://product.apis.f5.com/ee/v1/entitlements/telemetry/bulk' \
    --header "Authorization: Bearer $(cat /path/to/jwt-file)" \
    --form 'file=@"<PATH_TO_REPORT>.zip"'
    ```

    After running this command, look for the "statusLink" in the response. The `report-id` is the last part of the "statusLink" value (the UUID). For example:

      ```json
      {"statusLink":"/status/2214e480-3401-43a3-a54c-9dc501a01f83"}
      ```

    In this example, the `report-id` is `2214e480-3401-43a3-a54c-9dc501a01f83`.

    Use your specific `report-id` in the following steps.

2. **Check the status of the usage acknowledgment**:

    Replace `<REPORT_ID>` with your specific ID from the previous response.

    ```shell
    curl --location 'https://product.apis.f5.com/ee/v1/entitlements/telemetry/bulk/status/{report_id}' \
    --header "Authorization: Bearer $(cat /path/to/jwt-file)"
    ```

3. **Download the usage acknowledgement from F5**:

    ```shell
    curl --location 'https://product.apis.f5.com/ee/v1/entitlements/telemetry/bulk/download/{report_id}' \
    --header "Authorization: Bearer $(cat /path/to/jwt-file)" \
    --output <PATH_TO_ACKNOWLEDGEMENT>.zip
    ```

4. **Upload the usage acknowledgement to NGINX Instance Manager**:

    ```shell
    curl -k --location 'https://<NIM_FQDN>/api/platform/v1/report/upload' \
    --header 'Authorization: Basic <BASE64_ENCODED_CREDENTIALS>' \
    --form 'file=@"<PATH_TO_ACKNOWLEDGEMENT>.zip"'
    ```

#### Web interface

### Add license and submit initial usage report with the web interface

#### Add license

To add a license:

1. Go to the FQDN of your NGINX Instance Manager host and log in.
1. Select the **Settings** (gear) icon. The **Licenses > Overview** opens at `https://<NIM_FQDN>/ui/settings/license`.
1. Select **Get Started**.
1. Select **Browse** or drag the file into the form.
1. Select **Add**.

#### Download initial usage report

Download the initial usage report to send to F5:

- On the **License > Overview** page, select **Download License Report**.

#### Submit usage report to F5

Submit the usage report to F5 and download the acknowledgment over REST. Follow steps 5–7 on the [REST](#add-license-submit-initial-usage-report) tab.

#### Upload the usage acknowledgement to NGINX Instance Manager

To upload the usage acknowledgement:

1. On the **License > Overview** page, select **Upload Usage Acknowledgement**.
2. Select **Browse** or drag the file into the form.
3. Select **Add**.


