diff --git a/src/pages/help/troubleshooting-client.mdx b/src/pages/help/troubleshooting-client.mdx
index 12abc84b..2d42e9b8 100644
--- a/src/pages/help/troubleshooting-client.mdx
+++ b/src/pages/help/troubleshooting-client.mdx
@@ -187,6 +187,60 @@ issue that recovers when restarting the client.
Once the bundle generation is complete, you can click on `Copy Key` to get the uploaded key and share with NetBird\'s team.
+### Remote debug bundle generation
+
+Administrators can remotely request debug bundles from peer clients through the Management API or Dashboard. This is
+particularly useful when troubleshooting issues on remote machines where local access is limited or when working with
+end-users who may not be familiar with command-line tools.
+
+When a remote debug bundle is requested:
+1. The management server sends a job request to the target peer
+2. The peer client receives the job and generates the debug bundle automatically
+3. The generated bundle is uploaded to a centralized location
+4. The administrator receives the upload key to access the bundle
+
+#### Using the Management API
+
+You can also trigger remote debug bundles programmatically via the Management API.
+
+See the [Peers API documentation](/ipa/resources/peers#create-peer-debug-bundle-job) for complete API reference, including:
+- Creating debug bundle jobs
+- Listing all jobs for a peer
+- Getting job status and upload keys
+
+#### Using the Dashboard
+
+You can trigger remote debug bundles directly from the NetBird Dashboard without requiring CLI access.
+
+**To generate a remote debug bundle:**
+
+1. Navigate to **Peers** in the dashboard
+2. Click on the peer you want to troubleshoot
+3. Click the **Run Remote Job** button (the peer must be online and connected)
+4. Select **Debug Bundle** from the dropdown menu
+5. Configure the debug bundle options:
+ - **Log File Count**: Number of log files to include (1-50, default: 10)
+ - **Enable Bundle Duration** (optional): Collect logs for a specific time period (1-5 minutes) before generating the bundle
+ - **Anonymize Log Data**: Remove sensitive information like IP addresses and domains
+6. Click **Create Debug Bundle**
+
+**Viewing job status and results:**
+
+Once triggered, the job appears in the **Remote Jobs** section on the peer details page. The table shows:
+- **Type**: The job type (Debug Bundle)
+- **Status**: Pending (yellow), Completed (green), or Failed (red)
+- **Created/Updated**: Timestamps for job lifecycle
+- **Output**: Once completed, displays the upload key that you can copy and share with NetBird support
+
+The upload key is automatically copyable by clicking on it. Share this key through GitHub Issues, Slack, or support channels.
+
+#### Limitations
+
+- The peer must be online and connected to the management server to receive the job
+- Debug bundle generation may take a few seconds to a few minutes depending on log size and system information
+- Bundles are automatically uploaded to NetBird's secure storage (or your configured upload endpoint for self-hosted deployments)
+- Upload keys expire after 30 days for security
+
## Enabling debug logs on agent
Logs can be temporarily set using the following command.
diff --git a/src/pages/ipa/resources/peers.mdx b/src/pages/ipa/resources/peers.mdx
index a8667fb1..d3d19a96 100644
--- a/src/pages/ipa/resources/peers.mdx
+++ b/src/pages/ipa/resources/peers.mdx
@@ -1597,8 +1597,807 @@ echo $response;
}
```
-
-
+
+
+
+
+
+---
+
+
+## Create Peer Debug Bundle Job {{ tag: 'POST' , label: '/api/peers/{peerId}/jobs' }}
+
+
+
+ Triggers a remote debug bundle generation on the specified peer. The peer must be online and connected to receive
+ the job. Once generated, the bundle is automatically uploaded to the configured storage location.
+
+ ### Path Parameters
+
+
+
+ The unique identifier of a peer
+
+
+
+ ### Request-Body Parameters
+
+
+
+ The job workload configuration
+
+
+
+
+ #### Workload Object
+
+
+
+ Job type. Use "bundle" for debug bundle generation
+
+
+
+
+ Job-specific parameters
+
+
+
+
+ #### Parameters Object (for debug bundle)
+
+
+
+ Anonymize IP addresses and non-netbird.io domains in logs and status output (default: false)
+
+
+
+
+ Enable time-based log collection before generating bundle (default: false)
+
+
+
+
+ Duration in minutes for log collection (1-5 minutes). Only used if bundle_for is true
+
+
+
+
+ Number of log files to include (1-1000, default: 10)
+
+
+
+
+
+
+
+
+
+```bash {{ title: 'cURL' }}
+curl -X POST https://api.netbird.io/api/peers/{peerId}/jobs \
+-H 'Accept: application/json' \
+-H 'Content-Type: application/json' \
+-H 'Authorization: Token ' \
+--data-raw '{
+ "workload": {
+ "type": "bundle",
+ "parameters": {
+ "anonymize": true,
+ "bundle_for": true,
+ "bundle_for_time": 2,
+ "log_file_count": 10
+ }
+ }
+}'
+```
+
+```js
+const axios = require('axios');
+let data = JSON.stringify({
+ "workload": {
+ "type": "bundle",
+ "parameters": {
+ "anonymize": true,
+ "bundle_for": true,
+ "bundle_for_time": 2,
+ "log_file_count": 10
+ }
+ }
+});
+let config = {
+ method: 'post',
+ maxBodyLength: Infinity,
+ url: '/api/peers/{peerId}/jobs',
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ 'Authorization': 'Token '
+ },
+ data : data
+};
+
+axios(config)
+.then((response) => {
+ console.log(JSON.stringify(response.data));
+})
+.catch((error) => {
+ console.log(error);
+});
+```
+
+```python
+import requests
+import json
+
+url = "https://api.netbird.io/api/peers/{peerId}/jobs"
+payload = json.dumps({
+ "workload": {
+ "type": "bundle",
+ "parameters": {
+ "anonymize": True,
+ "bundle_for": True,
+ "bundle_for_time": 2,
+ "log_file_count": 10
+ }
+ }
+})
+headers = {
+ 'Content-Type': 'application/json',
+ 'Accept': 'application/json',
+ 'Authorization': 'Token '
+}
+
+response = requests.request("POST", url, headers=headers, data=payload)
+
+print(response.text)
+```
+
+```go
+package main
+
+import (
+ "fmt"
+ "strings"
+ "net/http"
+ "io/ioutil"
+)
+
+func main() {
+
+ url := "https://api.netbird.io/api/peers/{peerId}/jobs"
+ method := "POST"
+
+ payload := strings.NewReader(`{
+ "workload": {
+ "type": "bundle",
+ "parameters": {
+ "anonymize": true,
+ "bundle_for": true,
+ "bundle_for_time": 2,
+ "log_file_count": 10
+ }
+ }
+}`)
+ client := &http.Client {
+ }
+ req, err := http.NewRequest(method, url, payload)
+
+ if err != nil {
+ fmt.Println(err)
+ return
+ {
+
+ req.Header.Add("Content-Type", "application/json")
+ req.Header.Add("Accept", "application/json")
+ req.Header.Add("Authorization", "Token ")
+
+ res, err := client.Do(req)
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+ defer res.Body.Close()
+
+ body, err := ioutil.ReadAll(res.Body)
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+ fmt.Println(string(body))
+}
+```
+
+```ruby
+require "uri"
+require "json"
+require "net/http"
+
+url = URI("https://api.netbird.io/api/peers/{peerId}/jobs")
+
+https = Net::HTTP.new(url.host, url.port)
+https.use_ssl = true
+
+request = Net::HTTP::Post.new(url)
+request["Content-Type"] = "application/json"
+request["Accept"] = "application/json"
+request["Authorization"] = "Token "
+
+request.body = JSON.dump({
+ "workload": {
+ "type": "bundle",
+ "parameters": {
+ "anonymize": true,
+ "bundle_for": true,
+ "bundle_for_time": 2,
+ "log_file_count": 10
+ }
+ }
+})
+response = https.request(request)
+puts response.read_body
+```
+
+```java
+OkHttpClient client = new OkHttpClient().newBuilder()
+ .build();
+MediaType mediaType = MediaType.parse("application/json");
+RequestBody body = RequestBody.create(mediaType, '{
+ "workload": {
+ "type": "bundle",
+ "parameters": {
+ "anonymize": true,
+ "bundle_for": true,
+ "bundle_for_time": 2,
+ "log_file_count": 10
+ }
+ }
+}');
+Request request = new Request.Builder()
+ .url("https://api.netbird.io/api/peers/{peerId}/jobs")
+ .method("POST", body)
+ .addHeader("Content-Type", "application/json")
+ .addHeader("Accept", "application/json")
+ .addHeader("Authorization: Token ")
+ .build();
+Response response = client.newCall(request).execute();
+```
+
+```php
+ 'https://api.netbird.io/api/peers/{peerId}/jobs',
+ CURLOPT_RETURNTRANSFER => true,
+ CURLOPT_ENCODING => '',
+ CURLOPT_MAXREDIRS => 10,
+ CURLOPT_TIMEOUT => 0,
+ CURLOPT_FOLLOWLOCATION => true,
+ CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
+ CURLOPT_CUSTOMREQUEST => 'POST',
+ CURLOPT_POSTFIELDS => '{
+ "workload": {
+ "type": "bundle",
+ "parameters": {
+ "anonymize": true,
+ "bundle_for": true,
+ "bundle_for_time": 2,
+ "log_file_count": 10
+ }
+ }
+}',
+ CURLOPT_HTTPHEADER => array(
+ 'Content-Type: application/json',
+ 'Accept: application/json',
+ 'Authorization: Token '
+ ),
+));
+
+$response = curl_exec($curl);
+
+curl_close($curl);
+echo $response;
+```
+
+
+
+
+
+```json {{ title: 'Example' }}
+{
+ "id": "chacbco6lnnbn6cg5s91",
+ "peer_id": "chacbco6lnnbn6cg5s90",
+ "status": "pending",
+ "created_at": "2026-01-21T10:30:00.000Z",
+ "updated_at": "2026-01-21T10:30:00.000Z",
+ "workload": {
+ "type": "bundle",
+ "parameters": {
+ "anonymize": true,
+ "bundle_for": true,
+ "bundle_for_time": 2,
+ "log_file_count": 10
+ }
+ }
+}
+```
+```json {{ title: 'Schema' }}
+{
+ "id": "string",
+ "peer_id": "string",
+ "status": "string",
+ "created_at": "string",
+ "updated_at": "string",
+ "workload": {
+ "type": "string",
+ "parameters": {
+ "anonymize": "boolean",
+ "bundle_for": "boolean",
+ "bundle_for_time": "number",
+ "log_file_count": "number"
+ },
+ "result": {
+ "upload_key": "string"
+ }
+ }
+}
+```
+
+
+
+
+
+
+---
+
+
+## List Peer Jobs {{ tag: 'GET' , label: '/api/peers/{peerId}/jobs' }}
+
+
+
+ Returns a list of all jobs for the specified peer, including debug bundle generation jobs.
+
+ ### Path Parameters
+
+
+
+ The unique identifier of a peer
+
+
+
+
+
+
+```bash {{ title: 'cURL' }}
+curl -X GET https://api.netbird.io/api/peers/{peerId}/jobs \
+-H 'Accept: application/json' \
+-H 'Authorization: Token '
+```
+
+```js
+const axios = require('axios');
+
+let config = {
+ method: 'get',
+ maxBodyLength: Infinity,
+ url: '/api/peers/{peerId}/jobs',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Token '
+ }
+};
+
+axios(config)
+.then((response) => {
+ console.log(JSON.stringify(response.data));
+})
+.catch((error) => {
+ console.log(error);
+});
+```
+
+```python
+import requests
+import json
+
+url = "https://api.netbird.io/api/peers/{peerId}/jobs"
+
+headers = {
+ 'Accept': 'application/json',
+ 'Authorization': 'Token '
+}
+
+response = requests.request("GET", url, headers=headers)
+
+print(response.text)
+```
+
+```go
+package main
+
+import (
+ "fmt"
+ "strings"
+ "net/http"
+ "io/ioutil"
+)
+
+func main() {
+
+ url := "https://api.netbird.io/api/peers/{peerId}/jobs"
+ method := "GET"
+
+ client := &http.Client {
+ }
+ req, err := http.NewRequest(method, url, nil)
+
+ if err != nil {
+ fmt.Println(err)
+ return
+ {
+
+ req.Header.Add("Accept", "application/json")
+ req.Header.Add("Authorization", "Token ")
+
+ res, err := client.Do(req)
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+ defer res.Body.Close()
+
+ body, err := ioutil.ReadAll(res.Body)
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+ fmt.Println(string(body))
+}
+```
+
+```ruby
+require "uri"
+require "json"
+require "net/http"
+
+url = URI("https://api.netbird.io/api/peers/{peerId}/jobs")
+
+https = Net::HTTP.new(url.host, url.port)
+https.use_ssl = true
+
+request = Net::HTTP::Get.new(url)
+request["Accept"] = "application/json"
+request["Authorization"] = "Token "
+
+response = https.request(request)
+puts response.read_body
+```
+
+```java
+OkHttpClient client = new OkHttpClient().newBuilder()
+ .build();
+
+Request request = new Request.Builder()
+ .url("https://api.netbird.io/api/peers/{peerId}/jobs")
+ .method("GET")
+ .addHeader("Accept", "application/json")
+ .addHeader("Authorization: Token ")
+ .build();
+Response response = client.newCall(request).execute();
+```
+
+```php
+ 'https://api.netbird.io/api/peers/{peerId}/jobs',
+ CURLOPT_RETURNTRANSFER => true,
+ CURLOPT_ENCODING => '',
+ CURLOPT_MAXREDIRS => 10,
+ CURLOPT_TIMEOUT => 0,
+ CURLOPT_FOLLOWLOCATION => true,
+ CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
+ CURLOPT_CUSTOMREQUEST => 'GET',
+ CURLOPT_HTTPHEADER => array(
+ 'Accept: application/json',
+ 'Authorization: Token '
+ ),
+));
+
+$response = curl_exec($curl);
+
+curl_close($curl);
+echo $response;
+```
+
+
+
+
+
+```json {{ title: 'Example' }}
+[
+ {
+ "id": "chacbco6lnnbn6cg5s91",
+ "peer_id": "chacbco6lnnbn6cg5s90",
+ "status": "succeeded",
+ "created_at": "2026-01-21T10:30:00.000Z",
+ "updated_at": "2026-01-21T10:31:15.000Z",
+ "workload": {
+ "type": "bundle",
+ "parameters": {
+ "anonymize": true,
+ "bundle_for": true,
+ "bundle_for_time": 2,
+ "log_file_count": 10
+ },
+ "result": {
+ "upload_key": "1234567890ab27fb37c88b3b4be7011e22aa2e5ca6f38ffa9c4481884941f726/12345678-90ab-cdef-1234-567890abcdef"
+ }
+ }
+ },
+ {
+ "id": "chacbco6lnnbn6cg5s92",
+ "peer_id": "chacbco6lnnbn6cg5s90",
+ "status": "pending",
+ "created_at": "2026-01-21T11:00:00.000Z",
+ "updated_at": "2026-01-21T11:00:00.000Z",
+ "workload": {
+ "type": "bundle",
+ "parameters": {
+ "anonymize": false,
+ "log_file_count": 10
+ }
+ }
+ }
+]
+```
+```json {{ title: 'Schema' }}
+[
+ {
+ "id": "string",
+ "peer_id": "string",
+ "status": "string",
+ "created_at": "string",
+ "updated_at": "string",
+ "workload": {
+ "type": "string",
+ "parameters": {
+ "anonymize": "boolean",
+ "bundle_for": "boolean",
+ "bundle_for_time": "number",
+ "log_file_count": "number"
+ },
+ "result": {
+ "upload_key": "string"
+ }
+ }
+ }
+]
+```
+
+
+
+
+
+
+---
+
+
+## Get Peer Job {{ tag: 'GET' , label: '/api/peers/{peerId}/jobs/{jobId}' }}
+
+
+
+ Returns details of a specific job for the peer, including the upload key if the debug bundle generation is complete.
+
+ ### Path Parameters
+
+
+
+ The unique identifier of a peer
+
+
+ The unique identifier of a job
+
+
+
+
+
+
+```bash {{ title: 'cURL' }}
+curl -X GET https://api.netbird.io/api/peers/{peerId}/jobs/{jobId} \
+-H 'Accept: application/json' \
+-H 'Authorization: Token '
+```
+
+```js
+const axios = require('axios');
+
+let config = {
+ method: 'get',
+ maxBodyLength: Infinity,
+ url: '/api/peers/{peerId}/jobs/{jobId}',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Token '
+ }
+};
+
+axios(config)
+.then((response) => {
+ console.log(JSON.stringify(response.data));
+})
+.catch((error) => {
+ console.log(error);
+});
+```
+
+```python
+import requests
+import json
+
+url = "https://api.netbird.io/api/peers/{peerId}/jobs/{jobId}"
+
+headers = {
+ 'Accept': 'application/json',
+ 'Authorization': 'Token '
+}
+
+response = requests.request("GET", url, headers=headers)
+
+print(response.text)
+```
+
+```go
+package main
+
+import (
+ "fmt"
+ "strings"
+ "net/http"
+ "io/ioutil"
+)
+
+func main() {
+
+ url := "https://api.netbird.io/api/peers/{peerId}/jobs/{jobId}"
+ method := "GET"
+
+ client := &http.Client {
+ }
+ req, err := http.NewRequest(method, url, nil)
+
+ if err != nil {
+ fmt.Println(err)
+ return
+ {
+
+ req.Header.Add("Accept", "application/json")
+ req.Header.Add("Authorization", "Token ")
+
+ res, err := client.Do(req)
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+ defer res.Body.Close()
+
+ body, err := ioutil.ReadAll(res.Body)
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+ fmt.Println(string(body))
+}
+```
+
+```ruby
+require "uri"
+require "json"
+require "net/http"
+
+url = URI("https://api.netbird.io/api/peers/{peerId}/jobs/{jobId}")
+
+https = Net::HTTP.new(url.host, url.port)
+https.use_ssl = true
+
+request = Net::HTTP::Get.new(url)
+request["Accept"] = "application/json"
+request["Authorization"] = "Token "
+
+response = https.request(request)
+puts response.read_body
+```
+
+```java
+OkHttpClient client = new OkHttpClient().newBuilder()
+ .build();
+
+Request request = new Request.Builder()
+ .url("https://api.netbird.io/api/peers/{peerId}/jobs/{jobId}")
+ .method("GET")
+ .addHeader("Accept", "application/json")
+ .addHeader("Authorization: Token ")
+ .build();
+Response response = client.newCall(request).execute();
+```
+
+```php
+ 'https://api.netbird.io/api/peers/{peerId}/jobs/{jobId}',
+ CURLOPT_RETURNTRANSFER => true,
+ CURLOPT_ENCODING => '',
+ CURLOPT_MAXREDIRS => 10,
+ CURLOPT_TIMEOUT => 0,
+ CURLOPT_FOLLOWLOCATION => true,
+ CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
+ CURLOPT_CUSTOMREQUEST => 'GET',
+ CURLOPT_HTTPHEADER => array(
+ 'Accept: application/json',
+ 'Authorization: Token '
+ ),
+));
+
+$response = curl_exec($curl);
+
+curl_close($curl);
+echo $response;
+```
+
+
+
+
+
+```json {{ title: 'Example' }}
+{
+ "id": "chacbco6lnnbn6cg5s91",
+ "peer_id": "chacbco6lnnbn6cg5s90",
+ "status": "succeeded",
+ "created_at": "2026-01-21T10:30:00.000Z",
+ "updated_at": "2026-01-21T10:31:15.000Z",
+ "workload": {
+ "type": "bundle",
+ "parameters": {
+ "anonymize": true,
+ "bundle_for": true,
+ "bundle_for_time": 2,
+ "log_file_count": 10
+ },
+ "result": {
+ "upload_key": "1234567890ab27fb37c88b3b4be7011e22aa2e5ca6f38ffa9c4481884941f726/12345678-90ab-cdef-1234-567890abcdef"
+ }
+ }
+}
+```
+```json {{ title: 'Schema' }}
+{
+ "id": "string",
+ "peer_id": "string",
+ "status": "string",
+ "created_at": "string",
+ "updated_at": "string",
+ "workload": {
+ "type": "string",
+ "parameters": {
+ "anonymize": "boolean",
+ "bundle_for": "boolean",
+ "bundle_for_time": "number",
+ "log_file_count": "number"
+ },
+ "result": {
+ "upload_key": "string"
+ }
+ }
+}
+```
+
+
+
diff --git a/src/pages/manage/activity/index.mdx b/src/pages/manage/activity/index.mdx
index eb7183ea..36ac7c2c 100644
--- a/src/pages/manage/activity/index.mdx
+++ b/src/pages/manage/activity/index.mdx
@@ -23,7 +23,7 @@ The current version of NetBird tracks a wide range of network changes that occur
Click here to view the full list of tracked events
- - **Peer Management:**
+ - **Peer Management:**
- Peer added by user
- Peer added with setup key
- Peer removed by user
@@ -33,6 +33,9 @@ The current version of NetBird tracks a wide range of network changes that occur
- Peer login expiration enabled
- Peer login expiration disabled
+ - **Remote Job Management:**
+ - Remote job created for peer
+
- **User Management:**
- User joined
- User invited