mc8yp 2.3.3 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs
CHANGED
|
@@ -1545,7 +1545,7 @@ const consola = createConsola();
|
|
|
1545
1545
|
//#endregion
|
|
1546
1546
|
//#region package.json
|
|
1547
1547
|
var name = "mc8yp";
|
|
1548
|
-
var version = "2.
|
|
1548
|
+
var version = "2.4.0";
|
|
1549
1549
|
var description$1 = "Cumulocity IoT MCP Server - Model Context Protocol integration for IoT device management";
|
|
1550
1550
|
//#endregion
|
|
1551
1551
|
//#region \0virtual:core-openapi
|
|
@@ -1564,7 +1564,7 @@ const specs = Object.freeze([
|
|
|
1564
1564
|
"altText": "Cumulocity",
|
|
1565
1565
|
"href": "https://www.cumulocity.com/api"
|
|
1566
1566
|
},
|
|
1567
|
-
"description": "# REST implementation\n\nThis section describes the aspects common to all REST-based interfaces of Cumulocity. The interfaces are based on the [Hypertext Transfer Protocol 1.1](https://tools.ietf.org/html/rfc2616) using [HTTPS](http://en.wikipedia.org/wiki/HTTP_Secure).\n\n## HTTP usage\n\n### Application management\n\nCumulocity uses a so-called \"application key\" to distinguish requests coming from devices and traffic from applications. If you write an application, pass the following header as part of all requests:\n\n```markup\nX-Cumulocity-Application-Key: <APPLICATION_KEY>\n```\n\nFor example, if you registered your application in the Cumulocity Administration application with the key \"myapp\", your requests should contain the header:\n\n```markup\nX-Cumulocity-Application-Key: myapp\n```\n\nThis makes your application subscribable and billable. If you implement a device, do not pass the key.\n\n> **ⓘ Info:** Make sure that you pass the key in **all** requests coming from an application. If you leave out the key,\n> the request will be considered as a device request, and the corresponding device will be marked as \"available\".\n\n### Limited HTTP clients\n\nIf you use an HTTP client that can only perform GET and POST methods in HTTP, you can emulate the other methods through an additional \"X-HTTP-METHOD\" header. Simply issue a POST request and add the header, specifying the actual REST method to be executed. For example, to emulate the \"PUT\" (modify) method, you can use:\n\n```http\nPOST ...\nX-HTTP-METHOD: PUT\n```\n\n### Processing mode\n\nEvery update request (PUT, POST, DELETE) executes with a so-called *processing mode*. The processing modes are as follows:\n\n|Processing mode|Description|\n|---|---|\n|PERSISTENT (default)|Stores data in the Cumulocity database and sends data to the Streaming Analytics engine. Afterwards, Cumulocity returns the result of the request. This is the default mode.|\n|TRANSIENT|Sends data to the Streaming Analytics engine and immediately returns the results asynchronously but does not store data in Cumulocity’s database. This mode saves storage and processing costs and is useful for example when tracking devices in real time without requiring data to be stored.|\n|QUIESCENT|Behaves similar to the persistent mode with the exception that no real-time notifications will be sent. The quiescent processing mode is applicable only for measurements and events.|\n|CEP| Behaves like the transient mode with the exception that no real-time notifications are sent. Currently it is applicable only for measurements and events.|\n\nTo explicitly control the processing mode of an update request, you can use the \"X-Cumulocity-Processing-Mode\" header with a value of either \"PERSISTENT\", \"TRANSIENT\", \"QUIESCENT\" or \"CEP\":\n\n```markup\nX-Cumulocity-Processing-Mode: PERSISTENT\n```\n\n> **ⓘ Info:** Events are always delivered to CEP/Apama for all processing modes. This is independent from real-time notifications.\n\n### Authorization\n\nAll requests issued to Cumulocity are subject to authorization. To determine the required permissions, see the \"Required role\" entries for the individual requests. To learn more about the different permissions and the concept of ownership in Cumulocity, see [Getting started > Technical concepts > Security aspects > Access control > Managing roles and assigning permissions](https://www.cumulocity.com/docs/concepts/security/#managing-roles-and-assigning-permissions) in the Cumulocity user documentation.\n\n### Media types\n\nEach type of data is associated with an own media type. The general format of media types is:\n\n```markup\napplication/vnd.com.nsn.cumulocity.<TYPE>+json;ver=<VERSION>;charset=UTF-8\n```\n\nEach media type contains a parameter `ver` indicating the version of the type. At the time of writing, the latest version is \"0.9\". As an example, the media type for an error message in the current version is:\n\n```markup\napplication/vnd.com.nsn.cumulocity.error+json;ver=0.9;charset=UTF-8\n```\n\nMedia types are used in HTTP \"Content-Type\" and \"Accept\" headers. If you specify an \"Accept\" header in a POST or PUT request, the response will contain the newly created or updated object. If you do not specify the header, the response body will be empty.\n\nIf a media type without the `ver` parameter is given, the oldest available version will be returned by the server. If the \"Accept\" header contains the same media type in multiple versions, the server will return a representation in the latest supported version.\n\nNote that media type values should be treated as case insensitive.\n\n### Date format\n\nData exchanged with Cumulocity in HTTP requests and responses is encoded in [JSON format](http://www.ietf.org/rfc/rfc4627.txt) and [UTF-8](http://en.wikipedia.org/wiki/UTF-8) character encoding. Timestamps and dates are accepted and emitted by Cumulocity in [ISO 8601](http://www.w3.org/TR/NOTE-datetime) format:\n\n```markup\nDate: YYYY-MM-DD\nTime: hh:mm:ss±hh:mm\nTimestamp: YYYY-MM-DDThh:mm:ss±hh:mm\n```\n\nTo avoid ambiguity, all times and timestamps must include timezone information. Please take into account that the plus character \"+\" must be encoded as \"%2B\".\n\n### Response Codes\n\nCumulocity uses conventional HTTP response codes to indicate the success or failure of an API request. Codes in the `2xx` range indicate success. Codes in the `4xx` range indicate a user error. The response provides information on why the request failed (for example, a required parameter was omitted). Codes in the `5xx` range indicate an error with Cumulocity's servers ([these are very rare](https://cumulocity.com/docs/service-terms/service-level/)).\n\n#### HTTP status code summary\n\n|Code|Message|Description|\n|:---:|:---|:---|\n|200|OK|Everything worked as expected.|\n|201|Created|A managed object was created.|\n|204|No content|An object was removed.|\n|400|Bad Request|The request was unacceptable, often due to missing a required parameter.|\n|401|Unauthorized|Authentication has failed, or credentials were required but not provided.|\n|403|Forbidden|The authenticated user doesn't have permissions to perform the request.|\n|404|Not Found|The requested resource doesn't exist.|\n|405|Method not allowed|The employed HTTP method cannot be used on this resource (for example, using PUT on a read-only resource).|\n|406|Not Acceptable|The server could not produce a response matching the list of accepted types defined in the request.|\n|409|Conflict| The data is correct but it breaks some constraints (for example, application version limit is exceeded). |\n|422|Invalid data| Invalid data was sent on the request and/or a query could not be understood. |\n|422|Unprocessable Entity| The requested resource cannot be updated or mandatory fields are missing on the executed operation. |\n|500<br>503|Server Errors| Something went wrong on Cumulocity's end. |\n\n## REST usage\n\n### Interpretation of HTTP verbs\n\nThe semantics described in the [HTTP specification](http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9) are used:\n\n* POST creates a new resource. In the response \"Location\" header, the URI of the newly created resource is returned.\n* GET retrieves a resource.\n* PUT updates an existing resource with the contents of the request.\n* DELETE removes a resource. The response will be \"204 No Content\".\n\nIf a PUT request only contains parts of a resource (also known as fragments), only those parts are updated. To remove such a part, use a PUT request with a null value for it:\n\n```json\n{\n \"resourcePartName\": null\n}\n```\n\n> **ⓘ Info:** A PUT request cannot update sub-resources that are identified by a separate URI.\n\n### URI space and URI templates\n\nClients should not make assumptions on the layout of URIs used in requests, but construct URIs from previously returned URIs or URI templates. The [root interface](#tag/Platform-API) provides the entry point for clients.\n\nURI templates contain placeholders in curly braces (for example, `{type}`), which must be filled by the client to produce a URI. As an example, see the following excerpt from the event API response:\n\n```json\n{\n \"events\": {\n \"self\": \"https://<TENANT_DOMAIN>/event\"\n },\n \"eventsForSourceAndType\": \"https://<TENANT_DOMAIN>/event/events?type={type}&source={source}\"\n}\n```\n\nThe client must fill the `{type}` and `{source}` placeholders with the desired type and source devices of the events to be returned. The meaning of these placeholders is documented in the respective interface descriptions.\n\n### Interface structure\n\nIn general, Cumulocity REST resources are modeled according to the following pattern:\n\n* The starting point are API resources, which will provide access to the actual data through URIs and URI templates to collection resources. For example, the above event API resource provides the `events` URI and the `eventsForSourceAndType` URI to access collections of events.\n* Collection resources aggregate member resources and allow creating new member resources in the collection. For example, through the `events` collection resource, new events can be created.\n* Finally, individual resources can be edited.\n\n#### Query result paging\n\nCollection resources support paging of data to avoid passing huge data volumes in one block from client to server. GET requests to collections accept two query parameters:\n\n* `currentPage` defines the slice of data to be returned, starting with 1. By default, the first page is returned.\n* `pageSize` indicates how many entries of the collection should be returned. By default, 5 entries are returned. The upper limit for one page is currently 2,000 documents. Any larger requested page size is trimmed to the upper limit.\n* `withTotalElements` will yield the total number of elements in the statistics object. This is only applicable on [range queries](https://en.wikipedia.org/wiki/Range_query_(database)).\n* `withTotalPages` will yield the total number of pages in the statistics object. This is only applicable on [range queries](https://en.wikipedia.org/wiki/Range_query_(database)).\n\nFor convenience, collection resources provide `next` and `prev` links to retrieve the next and previous pages of the results. The following is an example response for managed object collections (the contents of the array `managedObjects` have been omitted):\n\n```json\n{\n \"self\" : \"https://<TENANT_DOMAIN>/inventory/managedObjects?pageSize=5¤tPage=2\",\n \"managedObjects\" : [...],\n \"statistics\" : {\n \"totalPages\" : 7,\n \"pageSize\" : 5,\n \"currentPage\" : 2,\n \"totalElements\" : 34\n },\n \"prev\" : \"https://<TENANT_DOMAIN>/inventory/managedObjects?pageSize=5¤tPage=1\",\n \"next\" : \"https://<TENANT_DOMAIN>/inventory/managedObjects?pageSize=5¤tPage=3\"\n}\n```\n\nThe `totalPages` and `totalElements` properties can be expensive to compute, hence they are not returned by default for [range queries](https://en.wikipedia.org/wiki/Range_query_(database)). To include any of them in the result, add the query parameters `withTotalPages=true` and/or `withTotalElements=true`.\n\n> **ⓘ Info:** If inventory roles are applied to a user, a query by the user may return less than `pageSize` results even if there are more results in total.\n\n> **ⓘ Info:** To improve performance, the `totalPages` and `totalElements` statistics are cached for 10 seconds.\n\n#### Query result paging for users with restricted access\n\nIf a user does not have a global role for reading data from the API resource but rather has [inventory roles](https://www.cumulocity.com/docs/standard-tenant/managing-permissions/#inventory-roles) for reading only particular documents, there are some differences in query result paging:\n\n* In some circumstances the response may contain less than `pageSize` and `totalElements` elements though there is more data in the database accessible for the user.\n* In some circumstances `next` and `prev` links may appear in the response though there is no more data in the database accessible for the user.\n* The property `currentPage` of the response does not contain the page number but the offset of the next element not yet processed by the querying mechanism.\n* The query parameters `withTotalPages=true` and `withTotalElements=true` have no effect, and the value of the `totalPages` and `totalElements` properties is always null.\n\nThe above behavior results from the fact that the querying mechanism is iterating maximally over 10 * max(pageSize, 100) documents per request, and it stops even though the full page of data accessible for the user could not be collected. When the next page is requested the querying mechanism starts the iteration where it completed the previous time.\n\n#### Query result by time interval\n\nUse the following query parameters to obtain data for a specified time interval:\n\n* `dateFrom` - Start date or date and time.\n* `dateTo` - End date or date and time.\n\nExample formats:\n\n```markup\ndateTo=2019-04-20\ndateTo=2019-04-20T08:30:00.000Z\n```\n\nParameters are optional. Values provided with those parameters are closed open range ( `[ dateFrom, dateTo )` ) . `dateFrom` is inclusive and `dateTo` is exclusive.\n\n> **⚠️ Important:** If your servers are not running in UTC (Coordinated Universal Time), any date passed without timezone will be handled as UTC, regardless of the server local timezone. This might lead to a difference regarding the date/time range included in the results.\n\n### Root interface\n\nTo discover the URIs to the various interfaces of Cumulocity, it provides a \"root\" interface.\nThis root interface aggregates all the underlying API resources.\nSee the [Platform API](#tag/Platform-API) endpoint.\nFor more information on the different API resources, consult the respective API sections.\n\n## Generic media types\n\n### Error\n\nThe error type provides further information on the reason of a failed request.\n\nContent-Type: application/vnd.com.nsn.cumulocity.error+json\n\n|Name|Type|Description|\n|---|---|---|\n|error|string|Error type formatted as `<RESOURCE_TYPE>/<ERROR_NAME>`. For example, an object not found in the inventory is reported as `inventory/notFound`.|\n|info|string|URL to an error description on the Internet.|\n|message|string|Short text description of the error|\n\n### Paging statistics\n\nPaging statistics for collection of resources.\n\nContent-Type: application/vnd.com.nsn.cumulocity.pagingstatistics+json\n\n|Name|Type|Description|\n|---|---|---|\n|currentPage|integer|The current returned page within the full result set, starting at \"1\".|\n|pageSize|integer|Maximum number of records contained in this query.|\n|totalElements|integer|The total number of results (elements).|\n|totalPages|integer|The total number of paginated results (pages).|\n\n> **ⓘ Info:** The `totalPages` and `totalElements` properties are not returned by default in the response. To include any of them, add the query parameters `withTotalPages=true` and/or `withTotalElements=true`. Be aware of [differences in query result paging for users with restricted access](#query-result-paging-for-users-with-restricted-access).\n\n> **ⓘ Info:** To improve performance, the `totalPages` and `totalElements` statistics are cached for 10 seconds.\n\n# Fragment library\n\nVisit the [Device management > Device integration > Fragment library](https://www.cumulocity.com/docs/device-integration/fragment-library/) in the Cumulocity user documentation.\n\n# Login options\n\nWhen you sign up for an account on the [Cumulocity platform](https://www.cumulocity.com/), for example, by using a free trial, you will be provided with a dedicated URL address for your tenant. All requests to the platform must be authenticated employing your tenant ID, Cumulocity user (c8yuser for short) and password. Cumulocity offers the following forms of authentication:\n\n* Basic authentication (Basic)\n* OAI-Secure authentication (OAI-Secure)\n* SSO with authentication code grant (SSO)\n* JWT authentication with an access token from a IAM (JWT-IAM)\n\nYou can check your login options with a GET call to the endpoint <kbd><a href=\"#tag/Login-options\">/tenant/loginOptions</a></kbd>.\n"
|
|
1567
|
+
"description": "# REST implementation\n\nThis section describes the aspects common to all REST-based interfaces of Cumulocity. The interfaces are based on the [Hypertext Transfer Protocol 1.1](https://tools.ietf.org/html/rfc2616) using [HTTPS](http://en.wikipedia.org/wiki/HTTP_Secure).\n\n## HTTP usage\n\n### Application management\n\nCumulocity uses a so-called \"application key\" to distinguish requests coming from devices and traffic from applications. If you write an application, pass the following header as part of all requests:\n\n```markup\nX-Cumulocity-Application-Key: <APPLICATION_KEY>\n```\n\nFor example, if you registered your application in the Cumulocity Administration application with the key \"myapp\", your requests should contain the header:\n\n```markup\nX-Cumulocity-Application-Key: myapp\n```\n\nThis makes your application subscribable and billable. If you implement a device, do not pass the key.\n\n> **ⓘ Info:** Make sure that you pass the key in **all** requests coming from an application. If you leave out the key,\n> the request will be considered as a device request, and the corresponding device will be marked as \"available\".\n\n### Limited HTTP clients\n\nIf you use an HTTP client that can only perform GET and POST methods in HTTP, you can emulate the other methods through an additional \"X-HTTP-METHOD\" header. Simply issue a POST request and add the header, specifying the actual REST method to be executed. For example, to emulate the \"PUT\" (modify) method, you can use:\n\n```http\nPOST ...\nX-HTTP-METHOD: PUT\n```\n\n### Processing mode\n\nEvery update request (PUT, POST, DELETE) executes with a so-called *processing mode*. The processing modes are as follows:\n\n|Processing mode|Description|\n|---|---|\n|PERSISTENT (default)|Stores data in the Cumulocity database and sends data to the Streaming Analytics engine. Afterwards, Cumulocity returns the result of the request. This is the default mode.|\n|TRANSIENT|Sends data to the Streaming Analytics engine and immediately returns the results asynchronously but does not store data in Cumulocity’s database. This mode saves storage and processing costs and is useful for example when tracking devices in real time without requiring data to be stored.|\n|QUIESCENT|Behaves similar to the persistent mode with the exception that no real-time notifications will be sent.|\n|CEP|Behaves like the transient mode with the exception that no real-time notifications are sent.|\n\nTo explicitly control the processing mode of an update request, you can use the \"X-Cumulocity-Processing-Mode\" header with a value of either \"PERSISTENT\", \"TRANSIENT\", \"QUIESCENT\" or \"CEP\":\n\n```markup\nX-Cumulocity-Processing-Mode: PERSISTENT\n```\n\n> **ⓘ Info:** Events are always delivered to CEP/Apama for all processing modes. This is independent from real-time notifications.\n\n### Authorization\n\nAll requests issued to Cumulocity are subject to authorization. To determine the required permissions, see the \"Required role\" entries for the individual requests. To learn more about the different permissions and the concept of ownership in Cumulocity, see [Getting started > Technical concepts > Security aspects > Access control > Managing roles and assigning permissions](https://www.cumulocity.com/docs/concepts/security/#managing-roles-and-assigning-permissions) in the Cumulocity user documentation.\n\n### Media types\n\nEach type of data is associated with an own media type. The general format of media types is:\n\n```markup\napplication/vnd.com.nsn.cumulocity.<TYPE>+json;ver=<VERSION>;charset=UTF-8\n```\n\nEach media type contains a parameter `ver` indicating the version of the type. At the time of writing, the latest version is \"0.9\". As an example, the media type for an error message in the current version is:\n\n```markup\napplication/vnd.com.nsn.cumulocity.error+json;ver=0.9;charset=UTF-8\n```\n\nMedia types are used in HTTP \"Content-Type\" and \"Accept\" headers. If you specify an \"Accept\" header in a POST or PUT request, the response will contain the newly created or updated object. If you do not specify the header, the response body will be empty.\n\nIf a media type without the `ver` parameter is given, the oldest available version will be returned by the server. If the \"Accept\" header contains the same media type in multiple versions, the server will return a representation in the latest supported version.\n\nNote that media type values should be treated as case insensitive.\n\n### Date format\n\nData exchanged with Cumulocity in HTTP requests and responses is encoded in [JSON format](http://www.ietf.org/rfc/rfc4627.txt) and [UTF-8](http://en.wikipedia.org/wiki/UTF-8) character encoding. Timestamps and dates are accepted and emitted by Cumulocity in [ISO 8601](http://www.w3.org/TR/NOTE-datetime) format:\n\n```markup\nDate: YYYY-MM-DD\nTime: hh:mm:ss±hh:mm\nTimestamp: YYYY-MM-DDThh:mm:ss±hh:mm\n```\n\nTo avoid ambiguity, all times and timestamps must include timezone information. Please take into account that the plus character \"+\" must be encoded as \"%2B\".\n\n### Response Codes\n\nCumulocity uses conventional HTTP response codes to indicate the success or failure of an API request. Codes in the `2xx` range indicate success. Codes in the `4xx` range indicate a user error. The response provides information on why the request failed (for example, a required parameter was omitted). Codes in the `5xx` range indicate an error with Cumulocity's servers ([these are very rare](https://cumulocity.com/docs/service-terms/service-level/)).\n\n#### HTTP status code summary\n\n|Code|Message|Description|\n|:---:|:---|:---|\n|200|OK|Everything worked as expected.|\n|201|Created|A managed object was created.|\n|204|No content|An object was removed.|\n|400|Bad Request|The request was unacceptable, often due to missing a required parameter.|\n|401|Unauthorized|Authentication has failed, or credentials were required but not provided.|\n|403|Forbidden|The authenticated user doesn't have permissions to perform the request.|\n|404|Not Found|The requested resource doesn't exist.|\n|405|Method not allowed|The employed HTTP method cannot be used on this resource (for example, using PUT on a read-only resource).|\n|406|Not Acceptable|The server could not produce a response matching the list of accepted types defined in the request.|\n|409|Conflict| The data is correct but it breaks some constraints (for example, application version limit is exceeded). |\n|422|Invalid data| Invalid data was sent on the request and/or a query could not be understood. |\n|422|Unprocessable Entity| The requested resource cannot be updated or mandatory fields are missing on the executed operation. |\n|500<br>503|Server Errors| Something went wrong on Cumulocity's end. |\n\n## REST usage\n\n### Interpretation of HTTP verbs\n\nThe semantics described in the [HTTP specification](http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9) are used:\n\n* POST creates a new resource. In the response \"Location\" header, the URI of the newly created resource is returned.\n* GET retrieves a resource.\n* PUT updates an existing resource with the contents of the request.\n* DELETE removes a resource. The response will be \"204 No Content\".\n\nIf a PUT request only contains parts of a resource (also known as fragments), only those parts are updated. To remove such a part, use a PUT request with a null value for it:\n\n```json\n{\n \"resourcePartName\": null\n}\n```\n\n> **ⓘ Info:** A PUT request cannot update sub-resources that are identified by a separate URI.\n\n### URI space and URI templates\n\nClients should not make assumptions on the layout of URIs used in requests, but construct URIs from previously returned URIs or URI templates. The [root interface](#tag/Platform-API) provides the entry point for clients.\n\nURI templates contain placeholders in curly braces (for example, `{type}`), which must be filled by the client to produce a URI. As an example, see the following excerpt from the event API response:\n\n```json\n{\n \"events\": {\n \"self\": \"https://<TENANT_DOMAIN>/event\"\n },\n \"eventsForSourceAndType\": \"https://<TENANT_DOMAIN>/event/events?type={type}&source={source}\"\n}\n```\n\nThe client must fill the `{type}` and `{source}` placeholders with the desired type and source devices of the events to be returned. The meaning of these placeholders is documented in the respective interface descriptions.\n\n### Interface structure\n\nIn general, Cumulocity REST resources are modeled according to the following pattern:\n\n* The starting point are API resources, which will provide access to the actual data through URIs and URI templates to collection resources. For example, the above event API resource provides the `events` URI and the `eventsForSourceAndType` URI to access collections of events.\n* Collection resources aggregate member resources and allow creating new member resources in the collection. For example, through the `events` collection resource, new events can be created.\n* Finally, individual resources can be edited.\n\n#### Query result paging\n\nCollection resources support paging of data to avoid passing huge data volumes in one block from client to server. GET requests to collections accept two query parameters:\n\n* `currentPage` defines the slice of data to be returned, starting with 1. By default, the first page is returned.\n* `pageSize` indicates how many entries of the collection should be returned. By default, 5 entries are returned. The upper limit for one page is currently 2,000 documents. Any larger requested page size is trimmed to the upper limit.\n* `withTotalElements` will yield the total number of elements in the statistics object. This is only applicable on [range queries](https://en.wikipedia.org/wiki/Range_query_(database)).\n* `withTotalPages` will yield the total number of pages in the statistics object. This is only applicable on [range queries](https://en.wikipedia.org/wiki/Range_query_(database)).\n\nFor convenience, collection resources provide `next` and `prev` links to retrieve the next and previous pages of the results. The following is an example response for managed object collections (the contents of the array `managedObjects` have been omitted):\n\n```json\n{\n \"self\" : \"https://<TENANT_DOMAIN>/inventory/managedObjects?pageSize=5¤tPage=2\",\n \"managedObjects\" : [...],\n \"statistics\" : {\n \"totalPages\" : 7,\n \"pageSize\" : 5,\n \"currentPage\" : 2,\n \"totalElements\" : 34\n },\n \"prev\" : \"https://<TENANT_DOMAIN>/inventory/managedObjects?pageSize=5¤tPage=1\",\n \"next\" : \"https://<TENANT_DOMAIN>/inventory/managedObjects?pageSize=5¤tPage=3\"\n}\n```\n\nThe `totalPages` and `totalElements` properties can be expensive to compute, hence they are not returned by default for [range queries](https://en.wikipedia.org/wiki/Range_query_(database)). To include any of them in the result, add the query parameters `withTotalPages=true` and/or `withTotalElements=true`.\n\n> **ⓘ Info:** If inventory roles are applied to a user, a query by the user may return less than `pageSize` results even if there are more results in total.\n\n> **ⓘ Info:** To improve performance, the `totalPages` and `totalElements` statistics are cached for 10 seconds.\n\n#### Query result paging for users with restricted access\n\nIf a user does not have a global role for reading data from the API resource but rather has [inventory roles](https://www.cumulocity.com/docs/standard-tenant/managing-permissions/#inventory-roles) for reading only particular documents, there are some differences in query result paging:\n\n* In some circumstances the response may contain less than `pageSize` and `totalElements` elements though there is more data in the database accessible for the user.\n* In some circumstances `next` and `prev` links may appear in the response though there is no more data in the database accessible for the user.\n* The property `currentPage` of the response does not contain the page number but the offset of the next element not yet processed by the querying mechanism.\n* The query parameters `withTotalPages=true` and `withTotalElements=true` have no effect, and the value of the `totalPages` and `totalElements` properties is always null.\n\nThe above behavior results from the fact that the querying mechanism is iterating maximally over 10 * max(pageSize, 100) documents per request, and it stops even though the full page of data accessible for the user could not be collected. When the next page is requested the querying mechanism starts the iteration where it completed the previous time.\n\n#### Query result by time interval\n\nUse the following query parameters to obtain data for a specified time interval:\n\n* `dateFrom` - Start date or date and time.\n* `dateTo` - End date or date and time.\n\nExample formats:\n\n```markup\ndateTo=2019-04-20\ndateTo=2019-04-20T08:30:00.000Z\n```\n\nParameters are optional. Values provided with those parameters are closed open range ( `[ dateFrom, dateTo )` ) . `dateFrom` is inclusive and `dateTo` is exclusive.\n\n> **⚠️ Important:** If your servers are not running in UTC (Coordinated Universal Time), any date passed without timezone will be handled as UTC, regardless of the server local timezone. This might lead to a difference regarding the date/time range included in the results.\n\n### Root interface\n\nTo discover the URIs to the various interfaces of Cumulocity, it provides a \"root\" interface.\nThis root interface aggregates all the underlying API resources.\nSee the [Platform API](#tag/Platform-API) endpoint.\nFor more information on the different API resources, consult the respective API sections.\n\n## Generic media types\n\n### Error\n\nThe error type provides further information on the reason of a failed request.\n\nContent-Type: application/vnd.com.nsn.cumulocity.error+json\n\n|Name|Type|Description|\n|---|---|---|\n|error|string|Error type formatted as `<RESOURCE_TYPE>/<ERROR_NAME>`. For example, an object not found in the inventory is reported as `inventory/notFound`.|\n|info|string|URL to an error description on the Internet.|\n|message|string|Short text description of the error|\n\n### Paging statistics\n\nPaging statistics for collection of resources.\n\nContent-Type: application/vnd.com.nsn.cumulocity.pagingstatistics+json\n\n|Name|Type|Description|\n|---|---|---|\n|currentPage|integer|The current returned page within the full result set, starting at \"1\".|\n|pageSize|integer|Maximum number of records contained in this query.|\n|totalElements|integer|The total number of results (elements).|\n|totalPages|integer|The total number of paginated results (pages).|\n\n> **ⓘ Info:** The `totalPages` and `totalElements` properties are not returned by default in the response. To include any of them, add the query parameters `withTotalPages=true` and/or `withTotalElements=true`. Be aware of [differences in query result paging for users with restricted access](#query-result-paging-for-users-with-restricted-access).\n\n> **ⓘ Info:** To improve performance, the `totalPages` and `totalElements` statistics are cached for 10 seconds.\n\n# Fragment library\n\nVisit the [Device management > Device integration > Fragment library](https://www.cumulocity.com/docs/device-integration/fragment-library/) in the Cumulocity user documentation.\n\n# Login options\n\nWhen you sign up for an account on the [Cumulocity platform](https://www.cumulocity.com/), for example, by using a free trial, you will be provided with a dedicated URL address for your tenant. All requests to the platform must be authenticated employing your tenant ID, Cumulocity user (c8yuser for short) and password. Cumulocity offers the following forms of authentication:\n\n* Basic authentication (Basic)\n* OAI-Secure authentication (OAI-Secure)\n* SSO with authentication code grant (SSO)\n* JWT authentication with an access token from a IAM (JWT-IAM)\n\nYou can check your login options with a GET call to the endpoint <kbd><a href=\"#tag/Login-options\">/tenant/loginOptions</a></kbd>.\n"
|
|
1568
1568
|
},
|
|
1569
1569
|
"servers": [{
|
|
1570
1570
|
"description": "Cumulocity tenant",
|
|
@@ -1755,7 +1755,7 @@ const specs = Object.freeze([
|
|
|
1755
1755
|
},
|
|
1756
1756
|
{
|
|
1757
1757
|
"name": "About notifications 2.0",
|
|
1758
|
-
"description": "# Overview\n\nThe Notifications 2.0 API allows applications or microservices to receive and process notifications generated by the use of the Cumulocity REST or MQTT APIs (device management, measurements, alarms, events and other platform APIs) in a reliable manner.\n\nThe Cumulocity Messaging Service is an optional component of the Cumulocity platform that may need to be enabled before Notifications 2.0 can be used.\nChanges to the configuration of the load balancer or ingress controller may also be required to allow access to the Websocket endpoint used by Notifications 2.0.\n\nFor the shared public cloud instances of the Cumulocity platform, the Messaging Service is enabled by default on release 10.13 and above.\nFor dedicated and self-hosted instances, the Messaging Service and Notifications 2.0 are available for release 10.11 and above, but will need to be explicitly enabled.\n\nPlease contact [product support](https://www.cumulocity.com/docs/additional-resources/contacting-support/) to inquire about using the Messaging Service and Notifications 2.0 capabilities in your Cumulocity environment.\nSee the *Messaging Service - Installation & operations guide* for further technical details of the configuration required, but note that these tasks can only be performed by a Cumulocity platform operator, not by a normal user.\n\nNotifications 2.0 improves upon the [Real-time notification API](#tag/Real-time-notification-API) by providing stronger delivery semantics and ordering guarantees.\nIt is also intended to be simpler to use than the \"Bayeaux\" protocol used in the Real-time notification API.\nNew capabilities are added to Notifications 2.0 in each release of Cumulocity.\nHowever, it does not yet support all of the notifications available from the Real-time notification API so it is not yet a complete replacement for the older API.\nSee the rest of this section and the detailed API documentation for full details of the notifications supported by this release of the Notifications 2.0 API.\n\n> **⚠️ Caution:** If you assign Notification 2.0 roles or permissions to users, they can create Notification 2.0 subscriptions and receive notifications for any device, including those to which assigned inventory roles do not grant access, bypassing the inventory role RBAC.\n\n## Topics and subscriptions\n\nInternally, Notifications 2.0 uses a [publish-subscribe](https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern)\npattern, allowing use-cases to organize their desired selections of measurement, event, alarm, operation and/or inventory messages\ninto topics according to functional interest.\nCreating a Notification 2.0 [subscription](#tag/Subscriptions)\nin Cumulocity creates a publisher (internally), that forwards Cumulocity messages that it matches (based\non message qualities such as its source, type or even content, for example) to a specific topic.\nEach subscription can only forward messages to one topic, but multiple subscriptions can forward messages to the same topic.\n\n> **⚠️ Important:** The term 'subscription' is overloaded and can be confusing here.\n> It is called a subscription because internally the topic subscribes to a selection of Cumulocity messages - it does not relate to a Notification 2.0 end consumer.\n> This may be revised in a future release to avoid potential confusion.\n\nThe diagram 'Notification 2.0 topics and subscriptions' below shows\nthree subscriptions that have been created in Cumulocity and are forwarding notification messages into two topics in the Messaging Service.\n\nThe 'temperature' topic is receiving measurements from the leftmost and centrally depicted subscriptions.\nBoth of these have a \"mo\" (managed object) context and they both include messages from the measurement API only.\nA subscription with a managed object context can only forward messages from a specific managed object (such as a device).\nThe leftmost subscription is forwarding measurements from a device with source ID '12345' and\nthe centrally depicted subscription does the same but from device '67890'.\n\nThe 'alarms' topic is receiving alarms from the rightmost subscription. It has a tenant context and includes messages from\nthe alarm API. It forwards all alarms within the tenant that creates the subscription, regardless of how they are generated\n(you can create finer grained topics by using filters).\nThe forwarded alarms include those raised by Cumulocity itself, and those published via REST or MQTT from other components in, or attached to,\nthe platform, including any alarms raised by the depicted devices.\n\n\n\n## Notification 2.0 Service Quotas\n\nMessages processed by Notifications 2.0 are stored persistently by the Cumulocity Messaging Service until they have been delivered to, and acknowledged by, all interested consumers.\n\nTo optimize resource usage, the Messaging Service imposes storage limits and a message time-to-live (TTL) on persistently stored messages.\n\nSee the [service quotas](/service-terms/quotas/#realtime-apis) documentation for details of the default limits.\nThese limits are configurable on a per-tenant basis.\nIf your use case requires a different configuration, or if you have any questions or concerns, please contact [product support](https://cumulocity.com/docs/additional-resources/contacting-support/).\n\n### Message backlog quota\n\nPersistent messages are stored in a subscription “backlog” until they are delivered to the destination consumer attached to that subscription.\nThe maximum size of a backlog is determined by the “backlog quota” limit, which directly affects the number of messages that can be stored and therefore the resource consumption of the platform.\n\nFor Notifications 2.0, a separate backlog exists for every unique subscription name - that is, for every distinct topic - used with the `/notification2/subscriptions` API. This backlog is shared by all subscriptions using the same topic, and by all consumers attached to each topic. If the quota limit is reached, no new messages can be added to the backlog until some older messages have been delivered, or deleted due to their TTL expiring.\n\nIf the backlog for a Notifications 2.0 topic has reached its quota limit, any API request to the {{< product-c8y-iot >}} platform that would be published onto that topic will receive HTTP response code 500.\nFor example, a POST request to the `/measurement/measurements` API endpoint will return the 500 response code if there is a topic that should receive that message, but cannot do so because its backlog is full.\nNote that for requests using the PERSISTENT [processing mode](https://cumulocity.com/api/core/#section/REST-implementation/HTTP-usage), the {{< product-c8y-iot >}} operational store will still be updated.\nThis can lead to duplicated entries in the operational store if applications blindly retry failed requests.\n\n### Message time-to-live\n\nAny undelivered messages will be automatically deleted if they have been on the backlog for longer than the TTL limit. This policy helps to limit overall resource usage and reduces the need to process outdated data after a prolonged disconnection of a consumer.\n\nNo message will ever be deleted from the backlog unless it reaches its TTL limit.\nMessages will always be delivered to the consumer in the order they were published to the topic.\n\n### Best practices to ensure reliable operation\n\nThese best practices will help to ensure that Notifications 2.0 can reliably deliver messages to consumers, and avoid requests failing due to reaching the backlog quota limit:\n\n* Consumers with messages unconsumed and unacknowledged is a common reason for backlogs to fill up, often leading to the unexpected failure of apparently unrelated {{< product-c8y-iot >}} platform requests.\n Therefore, monitor all attached consumers and ensure that messages are processed and acknowledged promptly.\n The [monitoring & management capability](https://cumulocity.com/docs/standard-tenant/monitoring/#monitoring-notifications-2.0) capability provides a convenient way to monitor topics and consumers.\n* If messages are published but unacknowledged within the TTL and the backlog is full, consider requesting a larger backlog quota or TTL.\n Higher message rates might require a larger backlog to cope with reasonable levels of unconsumed and unacknowledged messages by attached consumers.\n For slow consumers, a larger TTL may be required to prevent messages from being deleted before they can be delivered.\n* Consider adjusting the filters on Notifications 2.0 subscriptions to send fewer messages, if this can be done while still delivering all the necessary messages to consumers.\n\n## Consumers and tokens\n\nA topic's notification messages can be received by WebSocket based consumers that present a valid authorizing\ntoken for that specific topic when connecting to the Notification 2.0 WebSocket endpoint.\nThis token is in the form of a string conforming to the JWT (JSON Web Token) standard. Tokens can be obtained from the\nCumulocity [token](#tag/Tokens) REST API by authenticated users.\n\nConsumers receive the topic messages reliably, with at-least-once semantics, in order and must acknowledge each message in turn.\nNotification order is preserved from the point of view of any given device sending in REST and MQTT API requests.\nThe protocol is text-based and described in detail in the [Consumer protocol](#section/Consumer-protocol) section.\nIn typical usage, multiple consumers of a given topic operate independently in parallel, each receiving and acknowledging\nseparate copies of those messages.\n\n> **⚠️ Important:** It is important to manage consumers carefully as they can place significant storage resource demands on a system.\n> Only create (connect) consumers if they are to be active, and unsubscribe them if they are no longer needed or not needed for long periods.\n> Unsubscribing is an explicit action - disconnecting a consumer client does not unsubscribe it.\n> See the [Consumer lifecycle](#section/Overview/Consumer-lifecycle) section for more details.\n\n\nThe diagram below shows three consumers that have been created in the Messaging Service by four consumer clients.\n\nThe rightmost client identifies its consumer as \"alarmmonitor\" and that consumer receives messages from the \"alarms\" subscription topic.\n\nThe second to right client identifies its consumer as \"tempaudit\" and that consumer receives messages from the \"temperature\" subscription topic.\n\nThe leftmost two consumer clients are two shares of a (logically single) [shared consumer](#section/Overview/Shared-consumer-tokens),\nand so share the same copy of topic messages. They both identify the same \"tempmonitor\" consumer; each receives a non-overlapping subset\nof the messages in the \"temperature\" topic.\nCollectively, they receive all of the messages in the topic.\n\n\n\n## Consumer lifecycle\n\nWhen a subscription is created, Cumulocity starts to create and forward notifications within the subscription's scope\nto the Messaging Service subsystem. The Message Service does not necessarily retain these messages; it only retains a\nsubscription topic's messages if the topic is persistent, and it has at least one consumer for the topic that is or has been connected.\n\n\n> **ⓘ Info:** The set of retained topic messages that have not been received and acknowledged by a given consumer are known as the consumer's *backlog*.\n>\n> Only consumers of persistent subscription topics have backlogs that are maintained across client reconnections.\n>\n> Consumers of non-persistent subscription topics get a new backlog pointing only to the next (latest) topic message when they connect/reconnect and any previous backlog is discarded, removing any message references. They can only cause backlog size problems if they remain connected but continually consume at a rate lower than the rate at which new messages arrive.\n\nPersistent subscription topic messages are only stored once in the Messaging Service, but all associated consumers' backlogs,\nwhether the consumer is connected or not, reference each message until they have received and acknowledged it.\nTherefore, all consumer backlogs should be considered to have a storage cost from when they first connect until\nthey explicitly express no further interest in the topic by unsubscribing.\nThe Messaging Service discards a given message only when there are no backlogs that reference it anymore.\n\nThe following diagram shows the lifecycle of a consumer backlog in relation to its associated client connection(s).\nThe backlog is created when the consumer first connects to the Messaging Service and is only destroyed when it is explicitly unsubscribed.\nThe consumer's backlog is maintained, even if its client connection is interrupted. This is needed for reliable messaging,\nallowing the consumer to not miss messages during connection outages, but comes at the cost of explicit lifecycle management.\n\n\n\nTo unsubscribe its consumer, pass its token as the **token** parameter to the [/notification2/unsubscribe](#operation/postNotificationTokenUnsubscribeResource) REST endpoint.\n\n\n## Creating subscriptions\n\nThe JSON fields sent in a [create subscription request](#operation/postNotificationSubscriptionResource)\ndetermine which Cumulocity messages are forwarded to a topic, and the forwarded message content.\nThis request must be made by an authenticated Cumulocity user with the `ROLE_NOTIFICATION_2_ADMIN` role.\n\nThe **context** field broadly determines the type of Cumulocity message a subscription might match and forward.\nThere are two valid **context** values: \"mo\" (managed object) and \"tenant\". Some subscription fields have\nconstraints that vary according to the value of the **context** field. Where this is the case, it is pointed out in that field's\ndocumentation in the [create subscription](#operation/postNotificationSubscriptionResource) documentation.\n\nThe **source** field can only be used if the **context** is \"mo\". It must have a nested **id** field containing the\nvalue of a managed object's global identifier (sometimes referred to as a \"device ID\" or \"source ID\").\nThis is used to target inclusion of messages from the given managed object.\n\n**subscription** is the first of two fields that identify which topic this subscription will forward messages to.\nMultiple subscriptions contribute to a single topic if they have the same values for both the **subscription** and **nonPersistent** fields.\n\nThe **nonPersistent** field determines if the topic is [persistent or non-persistent](#section/Overview/Persistent-and-non-persistent-subscriptions).\nSubscriptions with the same **subscription** field value, but different **nonPersistent** values forward to two different topics.\nIt is, therefore, the second of the two fields that identifies the subscription topic.\n\nThe **filter** field can provide a [subscription filter](#section/Overview/Subscription-filters), allowing more finely\ngrained selection of the included messages, based on the message **type** and API (is it a measurement or alarm, for example).\n\nThe **fragmentsToCopy** field allows the forwarded messages to be transformed so that they contain only a specific subset\nof the fragments present in the original Cumulocity messages they are generated from. This can be useful, for example,\nfor security, bandwidth saving or functionality scoping reasons.\n\nThe following summarizes the subscription fields.\n<table>\n<thead>\n<tr>\n<th nowrap=\"nowrap\">Field Name </th>\n<th>Value</th>\n<th>Required/Default</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td nowrap=\"nowrap\">context</td>\n<td>\"mo\" or \"tenant\"</td>\n<td>Required</td>\n<td>Only values \"mo\" and \"tenant\" are supported</td>\n</tr>\n<tr>\n<td nowrap=\"nowrap\">source</td>\n<td>A managed object global identifier</td>\n<td>Required if context is \"mo\"</td>\n<td>Has a mandatory child <b>id</b> field</td>\n</tr>\n<tr>\n<td nowrap=\"nowrap\">subscription</td>\n<td>String</td>\n<td>Required</td>\n<td>Determines messaging-service topic used</td>\n</tr>\n<tr>\n<td nowrap=\"nowrap\">nonPersistent</td>\n<td>Boolean</td>\n<td>false</td>\n<td>Determines if a persistent or non-persistent topic is used</td>\n</tr>\n<tr>\n<td nowrap=\"nowrap\">filter</td>\n<td>JSON object</td>\n<td>All messages</td>\n<td>Includes messages based on message values</td>\n</tr>\n<tr>\n<td nowrap=\"nowrap\">fragmentsToCopy</td>\n<td>JSON list of strings</td>\n<td>All fragments</td>\n<td>Determines which fragments are included in forwarded messages</td>\n</tr>\n</tbody>\n</table>\n\nNotifications for new managed objects creations can never be forwarded by subscriptions with an \"mo\" **context** as the\nmanaged object is only given an ID when it is created and that ID is needed as a field to create the subscription.\nTherefore, to receive notifications informing of new managed object creations, create a subscription with \"tenant\" **context**\nto listen for them.\nAn application can use this to discover new managed objects.\nIt can then choose to create subscriptions with \"mo\" **context** for those managed objects.\n\nSubscriptions with \"tenant\" **context** can also use the alarms API, the events API, and/or the operations API to forward all\nalarms, events, and/or operations, respectively, which occur within the tenant, applying filters as desired.\n\nThe following summarizes the context and API support.\n\n| Context | ManagedObject Create | ManagedObject Update & Delete | Alarms | Events | Measurements | Operations |\n|----------|----------------------|---------------------------------|-----------|-----------|---------------|--------------|\n| mo | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ |\n| tenant | ✓ | ✗ | ✓ | ✓ | ✗ | ✓ |\n\n## Subscription filters\n\nSubscription filters provide fine-grained selection of the Cumulocity messages a subscription will forward.\nFilters can be provided as the **subscriptionFilter** field in a subscription's JSON object at creation time.\nIt is a JSON object with **apis** and **typeFilter** fields.\nFilters can provide either or both filter fields.\n\nThe **apis** field is a JSON array that specifies which Cumulocity API messages to include.\nUse an array containing just the wildcard value, \"*\", to include messages from all APIs.\nTo include messages from a subset of the APIs, use an array containing any single or multiple selection from\n\"alarms\", \"alarmsWithChildren\", \"events\", \"eventsWithChildren\", \"measurements\", \"managedobjects\" and \"operations\".\n\nFor example, to include messages from all APIs:\n\n```json\n{\n \"context\": \"mo\",\n \"subscription\": \"subscription01\",\n \"source\": {\n \"id\": \"2468\"\n },\n \"filter\": {\n \"apis\": [\"*\"]\n }\n}\n```\n\nTo include only messages from the measurements and alarms APIs:\n\n```json\n{\n \"context\": \"mo\",\n \"subscription\": \"subscription02\",\n \"source\": {\n \"id\": \"2468\"\n },\n \"filter\": {\n \"apis\": [\"measurements\", \"alarms\"]\n }\n}\n```\n\nThe \"alarmsWithChildren\" and \"eventsWithChildren\" **apis** values allow subscriptions with managed object **context**\nto filter in, respectively, alarms or events for all recursively descendant child managed objects of the **source.id**\nmanaged object in addition to those from the **source.id** managed object itself.\n\nThe **typeFilter** string field is matched against the original message's **type** field. It can be a single value, or a\nlimited (supporting only `or`) [OData](https://en.wikipedia.org/wiki/Open_Data_Protocol) expression.\n\nFor example, to include messages with **type** \"temperature\" and messages with **type** \"pressure\":\n\n```json\n{\n \"context\": \"mo\",\n \"subscription\": \"subscription03\",\n \"source\": {\n \"id\": \"2468\"\n },\n \"filter\": {\n \"type\": \"'temperature' or 'pressure'\"\n }\n}\n```\n\nTo include messages of **type** \"temperature\" only:\n\n```json\n{\n \"context\": \"mo\",\n \"subscription\": \"subscription04\",\n \"source\": {\n \"id\": \"2468\"\n },\n \"filter\": {\n \"type\": \"'temperature'\"\n }\n}\n```\n\n## Persistent and non-persistent subscriptions\n\nA subscription can be persistent or non-persistent, implying that the Messaging Service topic for it is either persistent\nor non-persistent, respectively.\nThese have different qualities which can be useful to satisfy the varying needs of differing use-cases.\n\n\nPersistent subscriptions are the default. They are used for reliable messaging, ensuring that consumers never miss a\nmessage if their connection is interrupted.\nThey use replicated secondary storage to maintain backlogs (within the constraints of any configured storage limits)\nand to maintain the consumers' positions in their topics.\nWhen a consumer of a persistent subscription topic has their connection interrupted,\nwhether that is due to network issues or deliberate actions by the consumer,\nupon reconnection they will continue to receive notifications from the topic position they were at before the outage\n(specifically, from the message after the last one they acknowledged successfully before the outage).\n\nNon-persistent subscriptions are only buffered in memory. A client consumer's position is not persisted across\ninterruptions of the client connection.\nWhen a consumer of a non-persistent subscription has their connection interrupted,\nupon reconnection they will start receiving notifications from the most recent message of the subscription,\nmissing all other notifications that occurred during the connection outage.\nThis is the case for all such temporarily disconnected consumers,\neven if other consumers of the same non-persistent subscription\nare still receiving older messages that occurred while it was not connected.\n\n> **ⓘ Info:** If you create both a persistent and a non-persistent subscription with the same **subscription** field, they are separate, independent subscriptions, backed by separate topics.\n>\n> When creating a token for a non-persistent subscription topic, to access notifications from the correct topic, the token's **nonPersistent** field must be set to `true`.\n> As is the case for the subscription, this field defaults to `false`, meaning the token will be for a persistent subscription topic by default.\n\n## Deleting subscriptions\n\nDeleting a subscription prevents it from adding further notification messages to its associated topic. Many subscriptions\ncan contribute notifications to a given topic so this does not control the topic lifecycle.\nDeleting all the subscriptions associated with a topic ensures no more notifications are added to it. This does not\ndelete the topic either.\n\nEven though the topic no longer accumulates new messages, there may still be consumers draining\nthe last of the messages from it. When all messages are consumed by all consumers, the topic is empty and consumes\nnegligible space in the Messaging Service.\n\nSubscriptions can be deleted by sending a [delete subscription request](#operation/deleteNotificationSubscriptionResource), using the subscription's **id** as the URL filename. For example, to delete the subscription with ID 8765:\n\n```text\n DELETE /notification2/subscriptions/8765 HTTP/1.1\n Host: <HOST>\n Authentication: Basic: <AUTHENTICATION>\n```\n\nWhen a tenant is deleted from Cumulocity, all its subscriptions are deleted. However, topics and consumers may\nstill be active in the Messaging Service until all messages are consumed.\n\n## Creating Tokens\n\nThe JSON fields sent in a [create token request](#operation/postNotificationTokenResource)\ndetermine which subscription topic a consumer can receive messages from, the token's duration,\nthe [shared](#section/Overview/Shared-consumer-tokens) nature of the consumer, and an identifier for the consumer.\nThis request must be made by an authenticated Cumulocity user with the `ROLE_NOTIFICATION_2_ADMIN` role.\n\nThe **subscription** field aligns with the same field in the subscription object, so broadly specifies the subscription\ntopic the consumer will receive notifications from. The **nonPersistent** field is also a factor in identifying the topic.\n\nThe **nonPersistent** field aligns with the same field in the subscription object so identifies whether the subscription topic\nis [persistent or non-persistent](#section/Overview/Persistent-and-non-persistent-subscriptions) and therefore is a factor in identifying the topic only.\nThere is no such thing as a peristent or non-peristent consumer\nand tokens cannot affect the nature they experience of topics using this field.\n\nThe **subscriber** field provides a unique (within the scope of this topic) identity for the consumer, allowing\nit to be recognized across connection interruptions, so allowing message delivery to be resumed after such interruptions\nand thus be reliable.\n\nThe **shared** field determines if multiple clients can connect in parallel as the consumer identified in the\n**subscriber** field, acting collectively to share the notification message processing load.\n\nThe **expiresInMinutes** field is the period that this token remains valid for, defaulting to 1440 minutes (1 day).\n\nThe following summarizes the token fields.\n<table>\n<thead>\n<tr>\n<th nowrap=\"nowrap\">Field Name </th>\n<th>Value</th>\n<th>Required/Default</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td nowrap=\"nowrap\">subscription</td>\n<td>string</td>\n<td>Required</td>\n<td>Identifies topic</td>\n</tr>\n<tr>\n<td nowrap=\"nowrap\">nonPersistent</td>\n<td>Boolean</td>\n<td>false</td>\n<td>Must be true to identify a non-persistent topic</td>\n</tr>\n<tr>\n<td nowrap=\"nowrap\">subscriber</td>\n<td>String</td>\n<td>Required</td>\n<td>Identifies the consumer bearing this token</td>\n</tr>\n<tr>\n<td nowrap=\"nowrap\">shared</td>\n<td>Boolean</td>\n<td>false</td>\n<td>True if multiple clients can act collectively as this consumer</td>\n</tr>\n<tr>\n<td nowrap=\"nowrap\">expiresInMinutes</td>\n<td>Integer</td>\n<td>1440</td>\n<td>The token duration</td>\n</tr>\n</tbody>\n</table>\n\n## Token expiration\n\nThe period a token remains valid for (its lifetime) is determined at creation time by the optional **expiresInMinutes** field, which defaults to 1440 minutes (one day).\nThis can be used to limit exposure to potential security issues related to them being leaked to parties that are not authorized to access the system.\nConsequently, tokens may need to be re-created or refreshed periodically.\n\n> **ⓘ Info:** Tokens only allow a consumer to connect to the Messaging Service. If a token expires while its consumer is connected, the consumer is not automatically logged out or disconnected.\n>\n> If a consumer disconnects, and their token has expired, the token must be recreated or refreshed in order that the consumer can reconnect.\n\nTokens can be refreshed by calling the token create request with the same parameters as originally (a different **expiresInMinutes** value can be given if a different duration is desired).\nThe token string is a JWT (JSON Web Token) token so if the parameters used are not easily kept available, they can be extracted from the token string as outlined in the Java code below:\n\n```java\n// The token string's sections are delimited by dots\nString[] tokenParts = token.split(\"\\\\.\");\n// Base64 decode the second section\nbyte[] decoded = Base64.getDecoder().decode(tokenParts[1]);\n// Create a string from the decoded bytes to get a JSON string of the token fields\nString tokenJson = new String(decoded);\n// ... optionally create an Object from the string or bytes using a JSON parser\n```\n\nIf you are using the Cumulocity Java SDK [TokenApi](https://github.com/Cumulocity-IoT/cumulocity-clients-java/blob/develop/java-client/src/main/java/com/cumulocity/sdk/client/messaging/notifications/TokenApi.java) class, it has a public refresh method which does the above for you, internally on the client side.\n\n## Shared consumer tokens\n\nShared consumer tokens allow parallelization of the consumer client workload.\nThis is useful if the notifications would otherwise arrive at a higher rate than a single consuming client application can process them.\nIt has no impact on the rate of notification throughput within, and thus their egress from, Cumulocity core.\n\nWhen you create a token, you can add the optional Boolean body parameter `shared` to the request.\nIf it is set to `true`, the created token is shared.\nIf it is not present or `false`, the token is exclusive (not shared).\n\nIf a consumer's token is not shared, the consumer is an *exclusive* consumer.\nOnly one consumer client can connect using an exclusive token. An attempt to connect further consumers with the same exclusive token results in an error.\nAn exclusive consumer receives a copy of all notifications from the subscription topic its token is for.\n\nIf a consumer's token is shared, the consumer is a *shared consumer*. Additional consumer clients can connect using the same token.\nIf only one shared consumer client is connected, it receives a copy of all notifications from the subscription topic.\nAs additional consumer clients connect using the same token, the consumer notification load is rebalanced so that\neach consumer client receives a non-overlapping subset (share) of the notifications from the subscription topic.\nThe set of consumer clients sharing a token can be thought of as a single logical consumer.\nCollectively, the set receives all notifications for the subscription topic.\n\nThe notification load is spread across the shared consumer clients according to the **id** of the source that generated the notification, typically a device **id**.\nAll notifications for a given **id** are delivered to the same consumer. Each consumer may receive notifications for many different **id**s.\nThis means that there is no benefit using shared tokens unless the notifications feeding the subscription topic are coming from multiple sources.\nNote that the load spreading algorithm may result in an asymmetric balance of notification load across the shares when there are few source **id**s in the subscription topic.\nThe load should generally become more evenly distributed as the number of sources increases.\n\nTo keep the messages from a given set of source **id**s 'sticky' to a specific consumer client in the share in the face of connection interruptions,\nthe consumer clients can provide an optional `consumer` parameter in their connection URL string, in addition to their usual `token` parameter.\n\nFor example: two consumers identifying themselves as *instance1* and *instance2* connect using URL paths\n`notification2/consumer?token=xyz&consumer=instance1` and `notification2/consumer?token=xyz&consumer=instance2`.\n\nSubscriptions are always unaware of the nature and number of their consumers: any number of shared and exclusive tokens\ncan be created for the same subscription topic and they all operate independently,\neach receiving their own copy of the notifications.\nThis means you can have multiple shared tokens for the same subscription topic and their load is only divided within the scope of each shared token.\n\n## Building consuming applications and microservices\n\nA consumer client requires only a valid URL and JWT string to connect.\nIt can be implemented using any WebSocket library or programming language as the protocol is text-based and relatively simple.\nThe implementation can be a microservice running internally to, or an application running externally from, Cumulocity.\n\nJava developers do not need to code to the protocol specification directly.\nThe API and the protocol have been implemented in the [Cumulocity Clients Java API](https://github.com/Cumulocity-IoT/cumulocity-clients-java/tree/develop/java-client/src/main/java/com/cumulocity/sdk/client/messaging/notifications)\n. Examples using that can be found\nin the [cumulocity-examples repository](https://github.com/Cumulocity-IoT/cumulocity-examples/tree/develop/notification2-examples).\n\n\n# Consumer protocol\n\nThe Cumulocity Notifications 2.0 API uses a secure [WebSocket](https://en.wikipedia.org/wiki/WebSocket) to consume notifications generated by the Cumulocity API.\n\nThe new endpoint is accessible using the external Cumulocity fully qualified domain name of your Cumulocity environment and the standard SSL port 443 using a secure WebSocket connection. It is also available on the unsecured port 80 and to microservices using \"cumulocity:8111\" but in most cases a secure connection is preferred.\n\nThe [URI scheme](https://en.wikipedia.org/wiki/List_of_URI_schemes) therefore is \"wss\" and consumers use URLs starting with \"wss://\" followed by the fully qualified domain name of the Cumulocity environment or tenant, followed by a fixed URL path and a query string.\n\nThe fixed URL path is <kbd>/notification2/consumer/</kbd> and there are only two query string arguments:\n\n* `token` (required). Its value must be a valid token in the form of a JWT token string as returned by a create token request to the [Tokens methods](#tag/Tokens) of the Notifications 2.0 API. Including the token as a query string parameter avoids having to set an HTTP header which can be an issue for some WebSocket clients or proxies.\n\n* `consumer` (optional). Its value is a non blank unique name for the consumer.\n\nIn summary, the URLs used by consumers follow the following patterns:\n\n```\nwss://your.cumulocity.environment.fullqualifieddomainname/notification2/consumer/?token=yourJwtTokenRequestedFromNotification2TokenService\n```\n\nor\n\n```\nwss://your.cumulocity.environment.fullqualifieddomainname/notification2/consumer/?token=yourJwtTokenRequestedFromNotification2TokenService&consumer=aUniqueNameForThisConsumer\n```\n\n## WebSocket timeouts\n\nThere is a timeout of 5 minutes set on idle WebSocket connections after which the connection will be closed by the server side. Therefore the consumer must be prepared to handle closed connections which is required for fault-tolerant operation in any case. All consuming microservices or applications should handle the WebSocket being closed and re-connect as necessary. Alternatively, if you would like to keep the connection from being closed due to idle timeout, implement a ping-pong handler in the WebSocket consumer. For example, you can implement this mechanism in Jetty by following [Jetty Programming Guide > Client Libraries > WebSocket Client > WebSocket Session > Sending Ping/Pong](https://eclipse.dev/jetty/documentation/jetty-11/programming-guide/index.html#pg-websocket-session-ping). A few libraries also provide built-in support for keeping the connection open. [Java-WebSocket](https://github.com/TooTallNate/Java-WebSocket), for example, does this by sending the ping requests to the server every minute by default.\n\n\n## Notification acknowledgements\n\nThe WebSocket service sends a sequence of UTF-8 encoded textual notification messages to the consumer.\nEach notification message includes headers and a data payload.\nHeaders occur first in the message and are separated by new line characters. The data payload is last in the message,\nseparated from the last header by 2 new line characters, showing one blank line occurs between the last header and the payload.\nThe first header in the message is always the acknowledgement header.\n\nWhen the client has finished processing a notification message,\nit must send that message's acknowledgement header back to the server on the same connection the message was received on.\nSending the acknowledgement tells the server that the consumer has successfully received and processed that message,\nallowing the server to forget the message.\nEach acknowledgement is unique to a particular notification and consumer. Note that batch and cumulative acknowledgements are not supported.\n\nIf too many of a consumer's notifications (1000 by default) remain unacknowledged,\nthe flow of notification messages to that consumer will stop until some of its unacknowledged messages are acknowledged.\nIt is therefore best practise to process and acknowledge them quickly, to minimise the potential for a connection interruption causing a need to redeliver them.\nAn acknowledgement should not be sent until its notification has been successfully processed.\nOtherwise, for example, if the client crashed during or before such processing,\nthe message may be lost as acknowledged messages are not (usually) resent by the server.\n\nThe hello-world-notification-microservice example in the [cumulocity-examples repository](https://github.com/Cumulocity-IoT/cumulocity-examples/tree/develop/hello-world-notification-microservice)\nshows an example of how to send the acknowledgement back to the server in a self-contained WebSocket text message.\nIt should be sent without quotation marks, as it is not a JSON message, and without any trailing new-line characters.\n\n## Notification message header and content\n\nA notification (transmitted service to client) consists of a header and a body (similar to an HTTP request).\nThe header is one or more (in practice at least 3) lines of text, separated by a `\\n` (newline) character.\nThe end of the header is demarcated by a double new line `\\n\\n`.\n\nThe notification body follows the header.\nThis also consists of UTF text - for example a JSON document.\nIf the notification is binary data or includes binary data then it will be [Base64 encoded](https://en.wikipedia.org/wiki/Base64).\n\nThe header lines for a notification are as follows (separated by `\\n` newlines):\n\n* Required message identifier for message acknowledgement. This opaque value is an encoded binary 64 bit value. After the consumer has finished processing a notification, it must send this header back to the server to [acknowledge the notification](#section/Consumer-protocol/Notification-acknowledgements).\n\n* The [Notification description header](#notification-description-header) on the second header line. This is a string describing what type of notification this is and its source. Measurements (\"measurements\"), events (\"events\") and alarms (\"alarms\") are examples of notifications, as are inventory creates, updates and deletes (\"managedObjects\"). There is a direct correspondence with realtime notifications which features similar notification descriptions. These are not enumerated here and are expected to increase in number in the future. For REST API notifications, they follow a three-part format, separated by \"/\". For more details on notification descriptions see [Notification description header](#notification-description-header).\n\n* An action string is the third header. Examples are CREATE, UPDATE and DELETE. More actions may be added in the future. Together with the notification type they describe the logical event that generated the notification, such as a CREATE of an alarm or measurement.\n\nDepending on the second header there may be further headers to follow but currently notifications only use the above three.\nIn order to be future proof and forward compatible, we encourage consumer code to cope with more headers by parsing them out but ignoring them.\n\nSee the *hello-world-notification-microservice* example in the [cumulocity-examples repository](https://github.com/Cumulocity-IoT/cumulocity-examples/tree/develop/hello-world-notification-microservice) on how to do this.\n\nAfter the headers, the notification body follows as UTF-8 text. This is typically a JSON document.\n\n### Notification description header\n\nThe second header line is the notification description string in the form of a `/`-separated path. For API notifications descriptions have three parts: tenantId, type and sourceId.\n\n* tenantId - this the identifier for the tenant under which the notification was generated.\n\n* type - the platform type of notification generated. For example, event, measurement, alarm or managed object.\n\n* sourceId - the identifier of the \"source\" object that generated or is the subject of the notification. Source is a very loose term here, much as in \"event sourcing\" but generally indicates which managed object the notification is about.\n\nSome examples are provided in [Traces](#section/Traces) and backwards compatibility to real-time notifications is provided for.\n\n## Dealing with notification duplication\n\nWhen a WebSocket connection is lost, whether that is due to deliberate connection closure or connection failure,\nmessages that were received but not successfully acknowledged before the connection loss are sent to the consumer again when it reconnects.\nThis can result in duplicate messages being received by the consumer.\nThose duplicates can sometimes include acknowledged messages as these may be on-the-wire from the consumer to the server when the connection is lost.\n\nNotification messages do not contain any specific unique identifiers to aid in de-duplication.\nTherefore, any de-duplication of messages must be done by the consumer based upon the [Notification description header](#notification-description-header) and payload.\nNote that the acknowledgement header is not guaranteed to be unique across consumer reconnections, which is when duplicates are most likely.\n\nSome events are easy to de-duplicate, such as inventory events where a unique source object is first created and then deleted.\nIt will often be possible to use the notification message headers to determine these cases.\nHowever, inventory updates or logically sequenced events such as alarms and measurements will typically require application-specific understanding of the payload fields.\nIdeally the payload would include a field specifically for that purpose. If it is not possible to include such a field in the payload,\nthen other existing fields will have to be used, maybe on a best effort basis.\n\nA specific field in the payload would typically be a [UUID](https://en.wikipedia.org/wiki/Universally_unique_identifier) or increasing sequence number.\nThe latter may aid the efficiency of de-duplication by using ordering,\nthough the sequence numbers will be unrelated across publishers, typically IoT devices, when the messages are originated from more than one publisher.\nAny messages received from the same publisher with a lower sequence number than the last one processed from that publisher can be quickly discarded,\nassuming the sequence has not rolled over. It is also easier for a human to understand that the ordering is correct and all messages are present.\n\n# Traces\n\nThe following is a sample of a trace of messages. The dashed lines are not a part of the message, they are a visual aid to separate the messages in the trace.\n```\n------------------------\nCJ1eEAAgADAB\n/tenant-a170/managedobjects/111\nCREATE\n\n{\"additionParents\":{\"references\":[],\"self\":\"http://cumulocity.default.svc.cluster.local/inventory/managedObjects/111/additionParents\"},\"owner\":\"admin\",\"childDevices\":{\"references\":[],\"self\":\"http://cumulocity.default.svc.cluster.local/inventory/managedObjects/111/childDevices\"},\"childAssets\":{\"references\":[],\"self\":\"http://cumulocity.default.svc.cluster.local/inventory/managedObjects/111/childAssets\"},\"creationTime\":\"2021-09-03T12:28:58.692Z\",\"lastUpdated\":\"2021-09-03T12:28:58.692Z\",\"childAdditions\":{\"references\":[],\"self\":\"http://cumulocity.default.svc.cluster.local/inventory/managedObjects/111/childAdditions\"},\"name\":\"a switch\",\"assetParents\":{\"references\":[],\"self\":\"http://cumulocity.default.svc.cluster.local/inventory/managedObjects/111/assetParents\"},\"deviceParents\":{\"references\":[],\"self\":\"http://cumulocity.default.svc.cluster.local/inventory/managedObjects/111/deviceParents\"},\"self\":\"http://cumulocity.default.svc.cluster.local/inventory/managedObjects/111\",\"id\":\"111\",\"com_cumulocity_model_BinarySwitch\":{\"state\":\"ON\"}}\n------------------------\nCKReEAAgADAB\n/tenant-a170/measurements/111\nCREATE\n\n{\"self\":\"http://cumulocity.default.svc.cluster.local/measurement/measurements/117\",\"time\":\"2021-09-03T12:29:01.664Z\",\"id\":\"117\",\"source\":{\"self\":\"http://cumulocity.default.svc.cluster.local/inventory/managedObjects/111\",\"id\":\"111\"},\"type\":\"c8y_TenantUsageStatisticsMeasurement\",\"resourcesUsage\":{\"memory\":0,\"cpu\":0,\"usedBy\":[]}}\n------------------------\nCI9eEAAgADAB\n/tenant-a170/events/111\nCREATE\n\n{\"creationTime\":\"2021-09-03T12:29:01.932Z\",\"source\":{\"name\":\"a switch\",\"self\":\"http://cumulocity.default.svc.cluster.local/inventory/managedObjects/111\",\"id\":\"111\"},\"type\":\"com_cumulocity_model_DoorSensorEvent\",\"self\":\"http://cumulocity.default.svc.cluster.local/event/events/118\",\"time\":\"2021-09-03T12:29:01.664Z\",\"id\":\"118\",\"text\":\"Door sensor was triggered\"}\n------------------------\nCAAQACAAMAE=\n/tenant-a170/eventsWithChildren/111\nCREATE\n\n{\"creationTime\":\"2021-09-03T12:29:01.932Z\",\"source\":{\"name\":\"a switch\",\"self\":\"http://cumulocity.default.svc.cluster.local/inventory/managedObjects/111\",\"id\":\"111\"},\"type\":\"com_cumulocity_model_DoorSensorEvent\",\"self\":\"http://cumulocity.default.svc.cluster.local/event/events/118\",\"time\":\"2021-09-03T12:29:01.664Z\",\"id\":\"118\",\"text\":\"Door sensor was triggered\"}\n------------------------\nCLJeEAAgADAB\n/tenant-a170/managedobjects/111\nUPDATE\n\n{\"additionParents\":{\"references\":[],\"self\":\"http://cumulocity.default.svc.cluster.local/inventory/managedObjects/111/additionParents\"},\"owner\":\"admin\",\"childDevices\":{\"references\":[],\"self\":\"http://cumulocity.default.svc.cluster.local/inventory/managedObjects/111/childDevices\"},\"childAssets\":{\"references\":[],\"self\":\"http://cumulocity.default.svc.cluster.local/inventory/managedObjects/111/childAssets\"},\"creationTime\":\"2021-09-03T12:28:58.692Z\",\"lastUpdated\":\"2021-09-03T12:28:58.692Z\",\"childAdditions\":{\"references\":[],\"self\":\"http://cumulocity.default.svc.cluster.local/inventory/managedObjects/111/childAdditions\"},\"name\":\"a switch\",\"assetParents\":{\"references\":[],\"self\":\"http://cumulocity.default.svc.cluster.local/inventory/managedObjects/111/assetParents\"},\"deviceParents\":{\"references\":[],\"self\":\"http://cumulocity.default.svc.cluster.local/inventory/managedObjects/111/deviceParents\"},\"self\":\"http://cumulocity.default.svc.cluster.local/inventory/managedObjects/111\",\"id\":\"111\",\"c8y_ActiveAlarmsStatus\":{\"major\":1},\"com_cumulocity_model_BinarySwitch\":{\"state\":\"ON\"}}\n------------------------\nCMFeEAAgADAB\n/tenant-a170/alarms/111\nCREATE\n\n{\"severity\":\"MAJOR\",\"creationTime\":\"2021-09-03T12:29:02.092Z\",\"count\":1,\"history\":{\"auditRecords\":[],\"self\":\"http://cumulocity.default.svc.cluster.local/audit/auditRecords\"},\"source\":{\"name\":\"a switch\",\"self\":\"http://cumulocity.default.svc.cluster.local/inventory/managedObjects/111\",\"id\":\"111\"},\"type\":\"com_cumulocity_events_TamperEvent\",\"self\":\"http://cumulocity.default.svc.cluster.local/alarm/alarms/119\",\"time\":\"2021-09-03T12:29:01.664Z\",\"id\":\"119\",\"text\":\"Tamper sensor triggered\",\"status\":\"ACTIVE\",\"com_mycorp_MyProp\":{\"key1\":\"value1\"}}\n------------------------\nCLxdEBQgADAB\n/tenant-a170/alarms/111\nCREATE\n\n{\"severity\":\"MAJOR\",\"creationTime\":\"2021-09-03T12:29:02.092Z\",\"count\":1,\"history\":{\"auditRecords\":[],\"self\":\"http://cumulocity.default.svc.cluster.local/audit/auditRecords\"},\"source\":{\"name\":\"a switch\",\"self\":\"http://cumulocity.default.svc.cluster.local/inventory/managedObjects/111\",\"id\":\"111\"},\"type\":\"com_cumulocity_events_TamperEvent\",\"self\":\"http://cumulocity.default.svc.cluster.local/alarm/alarms/119\",\"time\":\"2021-09-03T12:29:01.664Z\",\"id\":\"119\",\"text\":\"Tamper sensor triggered\",\"status\":\"ACTIVE\",\"com_mycorp_MyProp\":{\"key1\":\"value1\"}}\n------------------------\nCMJdEAAgADAB\n/tenant-a170/alarmsWithChildren/111\nCREATE\n\n{\"severity\":\"MAJOR\",\"creationTime\":\"2021-09-03T12:29:02.092Z\",\"count\":1,\"history\":{\"auditRecords\":[],\"self\":\"http://cumulocity.default.svc.cluster.local/audit/auditRecords\"},\"source\":{\"name\":\"a switch\",\"self\":\"http://cumulocity.default.svc.cluster.local/inventory/managedObjects/111\",\"id\":\"111\"},\"type\":\"com_cumulocity_events_TamperEvent\",\"self\":\"http://cumulocity.default.svc.cluster.local/alarm/alarms/119\",\"time\":\"2021-09-03T12:29:01.664Z\",\"id\":\"119\",\"text\":\"Tamper sensor triggered\",\"status\":\"ACTIVE\",\"com_mycorp_MyProp\":{\"key1\":\"value1\"}}\n```\n"
|
|
1758
|
+
"description": "# Overview\n\nThe Notifications 2.0 API allows applications or microservices to receive and process notifications generated by the use of the Cumulocity REST or MQTT APIs (device management, measurements, alarms, events and other platform APIs) in a reliable manner.\n\nThe Cumulocity Messaging Service is an optional component of the Cumulocity platform that may need to be enabled before Notifications 2.0 can be used.\nChanges to the configuration of the load balancer or ingress controller may also be required to allow access to the Websocket endpoint used by Notifications 2.0.\n\nFor the shared public cloud instances of the Cumulocity platform, the Messaging Service is enabled by default on release 10.13 and above.\nFor dedicated and self-hosted instances, the Messaging Service and Notifications 2.0 are available for release 10.11 and above, but will need to be explicitly enabled.\n\nPlease contact [product support](https://www.cumulocity.com/docs/additional-resources/contacting-support/) to inquire about using the Messaging Service and Notifications 2.0 capabilities in your Cumulocity environment.\nSee the *Messaging Service - Installation & operations guide* for further technical details of the configuration required, but note that these tasks can only be performed by a Cumulocity platform operator, not by a normal user.\n\nNotifications 2.0 improves upon the [Real-time notification API](#tag/Real-time-notification-API) by providing stronger delivery semantics and ordering guarantees.\nIt is also intended to be simpler to use than the \"Bayeaux\" protocol used in the Real-time notification API.\nNew capabilities are added to Notifications 2.0 in each release of Cumulocity.\nHowever, it does not yet support all of the notifications available from the Real-time notification API so it is not yet a complete replacement for the older API.\nSee the rest of this section and the detailed API documentation for full details of the notifications supported by this release of the Notifications 2.0 API.\n\n> **⚠️ Caution:** If you assign Notification 2.0 roles or permissions to users, they can create Notification 2.0 subscriptions and receive notifications for any device, including those to which assigned inventory roles do not grant access, bypassing the inventory role RBAC.\n\n## Topics and subscriptions\n\nInternally, Notifications 2.0 uses a [publish-subscribe](https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern)\npattern, allowing use-cases to organize their desired selections of measurement, event, alarm, operation and/or inventory messages\ninto topics according to functional interest.\nCreating a Notification 2.0 [subscription](#tag/Subscriptions)\nin Cumulocity creates a publisher (internally), that forwards Cumulocity messages that it matches (based\non message qualities such as its source, type or even content, for example) to a specific topic.\nEach subscription can only forward messages to one topic, but multiple subscriptions can forward messages to the same topic.\n\n> **⚠️ Important:** The term 'subscription' is overloaded and can be confusing here.\n> It is called a subscription because internally the topic subscribes to a selection of Cumulocity messages - it does not relate to a Notification 2.0 end consumer.\n> This may be revised in a future release to avoid potential confusion.\n\nThe diagram 'Notification 2.0 topics and subscriptions' below shows\nthree subscriptions that have been created in Cumulocity and are forwarding notification messages into two topics in the Messaging Service.\n\nThe 'temperature' topic is receiving measurements from the leftmost and centrally depicted subscriptions.\nBoth of these have a \"mo\" (managed object) context and they both include messages from the measurement API only.\nA subscription with a managed object context can only forward messages from a specific managed object (such as a device).\nThe leftmost subscription is forwarding measurements from a device with source ID '12345' and\nthe centrally depicted subscription does the same but from device '67890'.\n\nThe 'alarms' topic is receiving alarms from the rightmost subscription. It has a tenant context and includes messages from\nthe alarm API. It forwards all alarms within the tenant that creates the subscription, regardless of how they are generated\n(you can create finer grained topics by using filters).\nThe forwarded alarms include those raised by Cumulocity itself, and those published via REST or MQTT from other components in, or attached to,\nthe platform, including any alarms raised by the depicted devices.\n\n\n\n## Notification 2.0 Service Quotas\n\nMessages processed by Notifications 2.0 are stored persistently by the Cumulocity Messaging Service until they have been delivered to, and acknowledged by, all interested consumers.\n\nTo optimize resource usage, the Messaging Service imposes storage limits and a message time-to-live (TTL) on persistently stored messages.\n\nSee the [service quotas](/service-terms/quotas/#realtime-apis) documentation for details of the default limits.\nThese limits are configurable on a per-tenant basis.\nIf your use case requires a different configuration, or if you have any questions or concerns, please contact [product support](https://cumulocity.com/docs/additional-resources/contacting-support/).\n\n### Message backlog quota\n\nPersistent messages are stored in a “backlog” until they are delivered to a destination consumer client attached to each consumer on a topic.\nThe maximum size of a backlog is determined by the “backlog quota” limit, which directly affects the number of messages that can be stored and therefore the resource consumption of the platform.\n\nFor Notifications 2.0, a separate backlog exists for every subscription name - that is, for every distinct topic - used with the `/notification2/subscriptions` API. This backlog is shared by all consumers using the same topic, and by all consumer clients attached to each topic. If the quota limit is reached, no new messages can be added to the backlog until some older messages have been delivered, or deleted due to their TTL expiring.\n\nIf the backlog for a Notifications 2.0 topic has reached its quota limit, any API request to the {{< product-c8y-iot >}} platform that would be published onto that topic will receive HTTP response code 500.\nFor example, a POST request to the `/measurement/measurements` API endpoint will return the 500 response code if there is a topic that should receive that message, but cannot do so because its backlog is full.\nNote that for requests using the PERSISTENT [processing mode](https://cumulocity.com/api/core/#section/REST-implementation/HTTP-usage), the {{< product-c8y-iot >}} operational store will still be updated.\nThis can lead to duplicated entries in the operational store if applications blindly retry failed requests.\n\n### Message time-to-live\n\nAny undelivered messages will be automatically deleted if they have been on the backlog for longer than the TTL limit. This policy helps to limit overall resource usage and reduces the need to process outdated data after a prolonged disconnection of a consumer.\n\nNo message will ever be deleted from the backlog unless it reaches its TTL limit.\nMessages will always be delivered to the consumer in the order they were published to the topic.\n\n### Best practices to ensure reliable operation\n\nThese best practices will help to ensure that Notifications 2.0 can reliably deliver messages to consumers, and avoid requests failing due to reaching the backlog quota limit:\n\n* Consumers with messages unconsumed and unacknowledged is a common reason for backlogs to fill up, often leading to the unexpected failure of apparently unrelated {{< product-c8y-iot >}} platform requests.\n Therefore, monitor all attached consumers and ensure that messages are processed and acknowledged promptly.\n The [monitoring & management capability](https://cumulocity.com/docs/standard-tenant/monitoring/#monitoring-notifications-2.0) capability provides a convenient way to monitor topics and consumers.\n* If messages are published but unacknowledged within the TTL and the backlog is full, consider requesting a larger backlog quota or TTL.\n Higher message rates might require a larger backlog to cope with reasonable levels of unconsumed and unacknowledged messages by attached consumers.\n For slow consumers, a larger TTL may be required to prevent messages from being deleted before they can be delivered.\n* Consider adjusting the filters on Notifications 2.0 subscriptions to send fewer messages, if this can be done while still delivering all the necessary messages to consumers.\n\n## Consumers and tokens\n\nA topic's notification messages can be received by WebSocket based consumers that present a valid authorizing\ntoken for that specific topic when connecting to the Notification 2.0 WebSocket endpoint.\nThis token is in the form of a string conforming to the JWT (JSON Web Token) standard. Tokens can be obtained from the\nCumulocity [token](#tag/Tokens) REST API by authenticated users.\n\nConsumers receive the topic messages reliably, with at-least-once semantics, in order and must acknowledge each message in turn.\nNotification order is preserved from the point of view of any given device sending in REST and MQTT API requests.\nThe protocol is text-based and described in detail in the [Consumer protocol](#section/Consumer-protocol) section.\nIn typical usage, multiple consumers of a given topic operate independently in parallel, each receiving and acknowledging\nseparate copies of those messages.\n\n> **⚠️ Important:** In this documentation, a _consumer_ is a persistent registration of interest in the messages on a topic.\n> A _consumer client_ is a message receiver, connected to a consumer, that can read and acknowledge the messages for that consumer.\n> When a consumer is created, the Messaging Service will retain messages on the backlog for the relevant topic, up to the limit of the backlog quota.\n> A consumer is created implicitly when the first consumer client is connected to it, but it must be explicitly deleted when no longer required.\n> Consumer clients may be connected or disconnected at any time without affecting message retention for the consumer.\n>\n> It is important to manage consumers carefully as they can place significant storage resource demands on a system.\n> Only create consumers if the messages will be consumed, and unsubscribe them if they are no longer needed or not needed for long periods.\n> Unsubscribing is an explicit action - disconnecting a consumer client does not unsubscribe it.\n> See the [Consumer lifecycle](#section/Overview/Consumer-lifecycle) section for more details.\n\n\nThe diagram below shows three consumers that have been created in the Messaging Service by four consumer clients.\n\nThe rightmost client identifies its consumer as \"alarmmonitor\" and that consumer receives messages from the \"alarms\" subscription topic.\n\nThe second to right client identifies its consumer as \"tempaudit\" and that consumer receives messages from the \"temperature\" subscription topic.\n\nThe leftmost two consumer clients are two shares of a (logically single) [shared consumer](#section/Overview/Shared-consumer-tokens),\nand so share the same copy of topic messages. They both identify the same \"tempmonitor\" consumer; each receives a non-overlapping subset\nof the messages in the \"temperature\" topic.\nCollectively, they receive all of the messages in the topic.\n\n\n\n## Consumer lifecycle\n\nWhen a subscription is created, Cumulocity starts to create and forward notifications within the subscription's scope\nto the Messaging Service subsystem. The Message Service does not necessarily retain these messages; it only retains a\nsubscription topic's messages if the topic is persistent, and it has at least one consumer for the topic that is or has been connected.\n\n\n> **ⓘ Info:** The set of retained topic messages that have not been received and acknowledged by a given consumer are known as the consumer's *backlog*.\n>\n> Only consumers of persistent subscription topics have backlogs that are maintained across client reconnections.\n>\n> Consumers of non-persistent subscription topics get a new backlog pointing only to the next (latest) topic message when they connect/reconnect and any previous backlog is discarded, removing any message references. They can only cause backlog size problems if they remain connected but continually consume at a rate lower than the rate at which new messages arrive.\n\nPersistent subscription topic messages are only stored once in the Messaging Service, but all associated consumers' backlogs,\nwhether the consumer is connected or not, reference each message until they have received and acknowledged it.\nTherefore, all consumer backlogs should be considered to have a storage cost from when they first connect until\nthey explicitly express no further interest in the topic by unsubscribing.\nThe Messaging Service discards a given message only when there are no backlogs that reference it anymore.\n\nThe following diagram shows the lifecycle of a consumer backlog in relation to its associated client connection(s).\nThe backlog is created when the consumer first connects to the Messaging Service and is only destroyed when it is explicitly unsubscribed.\nThe consumer's backlog is maintained, even if its client connection is interrupted. This is needed for reliable messaging,\nallowing the consumer to not miss messages during connection outages, but comes at the cost of explicit lifecycle management.\n\n\n\nTo unsubscribe its consumer, pass its token as the **token** parameter to the [/notification2/unsubscribe](#operation/postNotificationTokenUnsubscribeResource) REST endpoint.\n\n\n## Creating subscriptions\n\nThe JSON fields sent in a [create subscription request](#operation/postNotificationSubscriptionResource)\ndetermine which Cumulocity messages are forwarded to a topic, and the forwarded message content.\nThis request must be made by an authenticated Cumulocity user with the `ROLE_NOTIFICATION_2_ADMIN` role.\n\nThe **context** field broadly determines the type of Cumulocity message a subscription might match and forward.\nThere are two valid **context** values: \"mo\" (managed object) and \"tenant\". Some subscription fields have\nconstraints that vary according to the value of the **context** field. Where this is the case, it is pointed out in that field's\ndocumentation in the [create subscription](#operation/postNotificationSubscriptionResource) documentation.\n\nThe **source** field can only be used if the **context** is \"mo\". It must have a nested **id** field containing the\nvalue of a managed object's global identifier (sometimes referred to as a \"device ID\" or \"source ID\").\nThis is used to target inclusion of messages from the given managed object.\n\n**subscription** is the first of two fields that identify which topic this subscription will forward messages to.\nMultiple subscriptions contribute to a single topic if they have the same values for both the **subscription** and **nonPersistent** fields.\n\nThe **nonPersistent** field determines if the topic is [persistent or non-persistent](#section/Overview/Persistent-and-non-persistent-subscriptions).\nSubscriptions with the same **subscription** field value, but different **nonPersistent** values forward to two different topics.\nIt is, therefore, the second of the two fields that identifies the subscription topic.\n\nThe **filter** field can provide a [subscription filter](#section/Overview/Subscription-filters), allowing more finely\ngrained selection of the included messages, based on the message **type** and API (is it a measurement or alarm, for example).\n\nThe **fragmentsToCopy** field allows the forwarded messages to be transformed so that they contain only a specific subset\nof the fragments present in the original Cumulocity messages they are generated from. This can be useful, for example,\nfor security, bandwidth saving or functionality scoping reasons.\n\nThe following summarizes the subscription fields.\n<table>\n<thead>\n<tr>\n<th nowrap=\"nowrap\">Field Name </th>\n<th>Value</th>\n<th>Required/Default</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td nowrap=\"nowrap\">context</td>\n<td>\"mo\" or \"tenant\"</td>\n<td>Required</td>\n<td>Only values \"mo\" and \"tenant\" are supported</td>\n</tr>\n<tr>\n<td nowrap=\"nowrap\">source</td>\n<td>A managed object global identifier</td>\n<td>Required if context is \"mo\"</td>\n<td>Has a mandatory child <b>id</b> field</td>\n</tr>\n<tr>\n<td nowrap=\"nowrap\">subscription</td>\n<td>String</td>\n<td>Required</td>\n<td>Determines messaging-service topic used</td>\n</tr>\n<tr>\n<td nowrap=\"nowrap\">nonPersistent</td>\n<td>Boolean</td>\n<td>false</td>\n<td>Determines if a persistent or non-persistent topic is used</td>\n</tr>\n<tr>\n<td nowrap=\"nowrap\">filter</td>\n<td>JSON object</td>\n<td>All messages</td>\n<td>Includes messages based on message values</td>\n</tr>\n<tr>\n<td nowrap=\"nowrap\">fragmentsToCopy</td>\n<td>JSON list of strings</td>\n<td>All fragments</td>\n<td>Determines which fragments are included in forwarded messages</td>\n</tr>\n</tbody>\n</table>\n\nNotifications for new managed objects creations can never be forwarded by subscriptions with an \"mo\" **context** as the\nmanaged object is only given an ID when it is created and that ID is needed as a field to create the subscription.\nTherefore, to receive notifications informing of new managed object creations, create a subscription with \"tenant\" **context**\nto listen for them.\nAn application can use this to discover new managed objects.\nIt can then choose to create subscriptions with \"mo\" **context** for those managed objects.\n\nSubscriptions with \"tenant\" **context** can also use the alarms API, the events API, and/or the operations API to forward all\nalarms, events, and/or operations, respectively, which occur within the tenant, applying filters as desired.\n\nThe following summarizes the context and API support.\n\n| Context | ManagedObject Create | ManagedObject Update & Delete | Alarms | Events | Measurements | Operations |\n|----------|----------------------|---------------------------------|-----------|-----------|---------------|--------------|\n| mo | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ |\n| tenant | ✓ | ✗ | ✓ | ✓ | ✗ | ✓ |\n\n## Subscription filters\n\nSubscription filters provide fine-grained selection of the Cumulocity messages a subscription will forward.\nFilters can be provided as the **subscriptionFilter** field in a subscription's JSON object at creation time.\nIt is a JSON object with **apis** and **typeFilter** fields.\nFilters can provide either or both filter fields.\n\nThe **apis** field is a JSON array that specifies which Cumulocity API messages to include.\nUse an array containing just the wildcard value, \"*\", to include messages from all APIs.\nTo include messages from a subset of the APIs, use an array containing any single or multiple selection from\n\"alarms\", \"alarmsWithChildren\", \"events\", \"eventsWithChildren\", \"measurements\", \"managedobjects\" and \"operations\".\n\nFor example, to include messages from all APIs:\n\n```json\n{\n \"context\": \"mo\",\n \"subscription\": \"subscription01\",\n \"source\": {\n \"id\": \"2468\"\n },\n \"filter\": {\n \"apis\": [\"*\"]\n }\n}\n```\n\nTo include only messages from the measurements and alarms APIs:\n\n```json\n{\n \"context\": \"mo\",\n \"subscription\": \"subscription02\",\n \"source\": {\n \"id\": \"2468\"\n },\n \"filter\": {\n \"apis\": [\"measurements\", \"alarms\"]\n }\n}\n```\n\nThe \"alarmsWithChildren\" and \"eventsWithChildren\" **apis** values allow subscriptions with managed object **context**\nto filter in, respectively, alarms or events for all recursively descendant child managed objects of the **source.id**\nmanaged object in addition to those from the **source.id** managed object itself.\n\nThe **typeFilter** string field is matched against the original message's **type** field. It can be a single value, or a\nlimited (supporting only `or`) [OData](https://en.wikipedia.org/wiki/Open_Data_Protocol) expression.\n\nFor example, to include messages with **type** \"temperature\" and messages with **type** \"pressure\":\n\n```json\n{\n \"context\": \"mo\",\n \"subscription\": \"subscription03\",\n \"source\": {\n \"id\": \"2468\"\n },\n \"filter\": {\n \"type\": \"'temperature' or 'pressure'\"\n }\n}\n```\n\nTo include messages of **type** \"temperature\" only:\n\n```json\n{\n \"context\": \"mo\",\n \"subscription\": \"subscription04\",\n \"source\": {\n \"id\": \"2468\"\n },\n \"filter\": {\n \"type\": \"'temperature'\"\n }\n}\n```\n\n## Persistent and non-persistent subscriptions\n\nA subscription can be persistent or non-persistent, implying that the Messaging Service topic for it is either persistent\nor non-persistent, respectively.\nThese have different qualities which can be useful to satisfy the varying needs of differing use-cases.\n\n\nPersistent subscriptions are the default. They are used for reliable messaging, ensuring that consumers never miss a\nmessage if their connection is interrupted.\nThey use replicated secondary storage to maintain backlogs (within the constraints of any configured storage limits)\nand to maintain the consumers' positions in their topics.\nWhen a consumer of a persistent subscription topic has their connection interrupted,\nwhether that is due to network issues or deliberate actions by the consumer,\nupon reconnection they will continue to receive notifications from the topic position they were at before the outage\n(specifically, from the message after the last one they acknowledged successfully before the outage).\n\nNon-persistent subscriptions are only buffered in memory. A client consumer's position is not persisted across\ninterruptions of the client connection.\nWhen a consumer of a non-persistent subscription has their connection interrupted,\nupon reconnection they will start receiving notifications from the most recent message of the subscription,\nmissing all other notifications that occurred during the connection outage.\nThis is the case for all such temporarily disconnected consumers,\neven if other consumers of the same non-persistent subscription\nare still receiving older messages that occurred while it was not connected.\n\n> **ⓘ Info:** If you create both a persistent and a non-persistent subscription with the same **subscription** field, they are separate, independent subscriptions, backed by separate topics.\n>\n> When creating a token for a non-persistent subscription topic, to access notifications from the correct topic, the token's **nonPersistent** field must be set to `true`.\n> As is the case for the subscription, this field defaults to `false`, meaning the token will be for a persistent subscription topic by default.\n\n## Deleting subscriptions\n\nDeleting a subscription prevents it from adding further notification messages to its associated topic. Many subscriptions\ncan contribute notifications to a given topic so this does not control the topic lifecycle.\nDeleting all the subscriptions associated with a topic ensures no more notifications are added to it. This does not\ndelete the topic either.\n\nEven though the topic no longer accumulates new messages, there may still be consumers draining\nthe last of the messages from it. When all messages are consumed by all consumers, the topic is empty and consumes\nnegligible space in the Messaging Service.\n\nSubscriptions can be deleted by sending a [delete subscription request](#operation/deleteNotificationSubscriptionResource), using the subscription's **id** as the URL filename. For example, to delete the subscription with ID 8765:\n\n```text\n DELETE /notification2/subscriptions/8765 HTTP/1.1\n Host: <HOST>\n Authentication: Basic: <AUTHENTICATION>\n```\n\nWhen a tenant is deleted from Cumulocity, all its subscriptions are deleted. However, topics and consumers may\nstill be active in the Messaging Service until all messages are consumed.\n\n## Creating Tokens\n\nThe JSON fields sent in a [create token request](#operation/postNotificationTokenResource)\ndetermine which subscription topic a consumer can receive messages from, the token's duration,\nthe [shared](#section/Overview/Shared-consumer-tokens) nature of the consumer, and an identifier for the consumer.\nThis request must be made by an authenticated Cumulocity user with the `ROLE_NOTIFICATION_2_ADMIN` role.\n\nThe **subscription** field aligns with the same field in the subscription object, so broadly specifies the subscription\ntopic the consumer will receive notifications from. The **nonPersistent** field is also a factor in identifying the topic.\n\nThe **nonPersistent** field aligns with the same field in the subscription object so identifies whether the subscription topic\nis [persistent or non-persistent](#section/Overview/Persistent-and-non-persistent-subscriptions) and therefore is a factor in identifying the topic only.\nThere is no such thing as a peristent or non-peristent consumer\nand tokens cannot affect the nature they experience of topics using this field.\n\nThe **subscriber** field provides a unique (within the scope of this topic) identity for the consumer, allowing\nit to be recognized across connection interruptions, so allowing message delivery to be resumed after such interruptions\nand thus be reliable.\n\n> **⚠️ Important:** As noted in [topics and subscriptions](#section/Overview/Topics-and-subscriptions) the terms 'subscription' and 'subscriber' are overloaded and can cause confusion.\n> When using the token API, the 'subscriber' field refers to the _consumer_, a named, persistent registration of interest in the messages on a topic.\n> See [consumers and tokens](#section/Overview/Consumers-and-tokens) for more details.\n\nThe **shared** field determines if multiple clients can connect in parallel as the consumer identified in the\n**subscriber** field, acting collectively to share the notification message processing load.\n\nThe **expiresInMinutes** field is the period that this token remains valid for, defaulting to 1440 minutes (1 day).\n\nThe following summarizes the token fields.\n<table>\n<thead>\n<tr>\n<th nowrap=\"nowrap\">Field Name </th>\n<th>Value</th>\n<th>Required/Default</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td nowrap=\"nowrap\">subscription</td>\n<td>string</td>\n<td>Required</td>\n<td>Identifies topic</td>\n</tr>\n<tr>\n<td nowrap=\"nowrap\">nonPersistent</td>\n<td>Boolean</td>\n<td>false</td>\n<td>Must be true to identify a non-persistent topic</td>\n</tr>\n<tr>\n<td nowrap=\"nowrap\">subscriber</td>\n<td>String</td>\n<td>Required</td>\n<td>Identifies the consumer bearing this token</td>\n</tr>\n<tr>\n<td nowrap=\"nowrap\">shared</td>\n<td>Boolean</td>\n<td>false</td>\n<td>True if multiple clients can act collectively as this consumer</td>\n</tr>\n<tr>\n<td nowrap=\"nowrap\">expiresInMinutes</td>\n<td>Integer</td>\n<td>1440</td>\n<td>The token duration</td>\n</tr>\n</tbody>\n</table>\n\n## Token expiration\n\nThe period a token remains valid for (its lifetime) is determined at creation time by the optional **expiresInMinutes** field, which defaults to 1440 minutes (one day).\nThis can be used to limit exposure to potential security issues related to them being leaked to parties that are not authorized to access the system.\nConsequently, tokens may need to be re-created or refreshed periodically.\n\n> **ⓘ Info:** Tokens only allow a consumer to connect to the Messaging Service. If a token expires while its consumer is connected, the consumer is not automatically logged out or disconnected.\n>\n> If a consumer disconnects, and their token has expired, the token must be recreated or refreshed in order that the consumer can reconnect.\n\nTokens can be refreshed by calling the token create request with the same parameters as originally (a different **expiresInMinutes** value can be given if a different duration is desired).\nThe token string is a JWT (JSON Web Token) token so if the parameters used are not easily kept available, they can be extracted from the token string as outlined in the Java code below:\n\n```java\n// The token string's sections are delimited by dots\nString[] tokenParts = token.split(\"\\\\.\");\n// Base64 decode the second section\nbyte[] decoded = Base64.getDecoder().decode(tokenParts[1]);\n// Create a string from the decoded bytes to get a JSON string of the token fields\nString tokenJson = new String(decoded);\n// ... optionally create an Object from the string or bytes using a JSON parser\n```\n\nIf you are using the Cumulocity Java SDK [TokenApi](https://github.com/Cumulocity-IoT/cumulocity-clients-java/blob/develop/java-client/src/main/java/com/cumulocity/sdk/client/messaging/notifications/TokenApi.java) class, it has a public refresh method which does the above for you, internally on the client side.\n\n## Shared consumer tokens\n\nShared consumer tokens allow parallelization of the consumer client workload.\nThis is useful if the notifications would otherwise arrive at a higher rate than a single consuming client application can process them.\nIt has no impact on the rate of notification throughput within, and thus their egress from, Cumulocity core.\n\nWhen you create a token, you can add the optional Boolean body parameter `shared` to the request.\nIf it is set to `true`, the created token is shared.\nIf it is not present or `false`, the token is exclusive (not shared).\n\nIf a consumer's token is not shared, the consumer is an *exclusive* consumer.\nOnly one consumer client can connect using an exclusive token. An attempt to connect further consumers with the same exclusive token results in an error.\nAn exclusive consumer receives a copy of all notifications from the subscription topic its token is for.\n\nIf a consumer's token is shared, the consumer is a *shared consumer*. Additional consumer clients can connect using the same token.\nIf only one shared consumer client is connected, it receives a copy of all notifications from the subscription topic.\nAs additional consumer clients connect using the same token, the consumer notification load is rebalanced so that\neach consumer client receives a non-overlapping subset (share) of the notifications from the subscription topic.\nThe set of consumer clients sharing a token can be thought of as a single logical consumer.\nCollectively, the set receives all notifications for the subscription topic.\n\nThe notification load is spread across the shared consumer clients according to the **id** of the source that generated the notification, typically a device **id**.\nAll notifications for a given **id** are delivered to the same consumer. Each consumer may receive notifications for many different **id**s.\nThis means that there is no benefit using shared tokens unless the notifications feeding the subscription topic are coming from multiple sources.\nNote that the load spreading algorithm may result in an asymmetric balance of notification load across the shares when there are few source **id**s in the subscription topic.\nThe load should generally become more evenly distributed as the number of sources increases.\n\nTo keep the messages from a given set of source **id**s 'sticky' to a specific consumer client in the share in the face of connection interruptions,\nthe consumer clients can provide an optional `consumer` parameter in their connection URL string, in addition to their usual `token` parameter.\n\nFor example: two consumer clients identifying themselves as *instance1* and *instance2* connect using URL paths\n`notification2/consumer?token=xyz&consumer=instance1` and `notification2/consumer?token=xyz&consumer=instance2`.\n\nSubscriptions are always unaware of the nature and number of their consumers: any number of shared and exclusive tokens\ncan be created for the same subscription topic and they all operate independently,\neach receiving their own copy of the notifications.\nThis means you can have multiple shared tokens for the same subscription topic and their load is only divided within the scope of each shared token.\n\n> **ⓘ Info:** Please note the following points to avoid unexpected behaviour when using shared consumer tokens:\n> * The `consumer` parameter to the `notification2/consumer` API endpoint provides a name for the _consumer client_, **not** the _consumer_.\n> The consumer name is encoded within the token string and does not have to be explicitly provided again.\n> * Multiple consumers on a topic operate independently, with each receiving their own copy of the notifications.\n> With shared consumers, this means that the distribution of notifications across consumer clients may be done differently for each consumer.\n> * Messages received by a shared consumer client **must** be acknowledged on the same WebSocket connection.\n> Attempting to acknowledge a message received by one consumer client on the connection used by a different client will not work.\n> The message will not be acknowledged and will remain on the backlog, potentially causing the topic backlog quota limit to be reached.\n\n## Building consuming applications and microservices\n\nA consumer client requires only a valid URL and JWT string to connect.\nIt can be implemented using any WebSocket library or programming language as the protocol is text-based and relatively simple.\nThe implementation can be a microservice running internally to, or an application running externally from, Cumulocity.\n\nJava developers do not need to code to the protocol specification directly.\nThe API and the protocol have been implemented in the [Cumulocity Clients Java API](https://github.com/Cumulocity-IoT/cumulocity-clients-java/tree/develop/java-client/src/main/java/com/cumulocity/sdk/client/messaging/notifications)\n. Examples using that can be found\nin the [cumulocity-examples repository](https://github.com/Cumulocity-IoT/cumulocity-examples/tree/develop/notification2-examples).\n\n\n# Consumer protocol\n\nThe Cumulocity Notifications 2.0 API uses a secure [WebSocket](https://en.wikipedia.org/wiki/WebSocket) to consume notifications generated by the Cumulocity API.\n\nThe new endpoint is accessible using the external Cumulocity fully qualified domain name of your Cumulocity environment and the standard SSL port 443 using a secure WebSocket connection. It is also available on the unsecured port 80 and to microservices using \"cumulocity:8111\" but in most cases a secure connection is preferred.\n\nThe [URI scheme](https://en.wikipedia.org/wiki/List_of_URI_schemes) therefore is \"wss\" and consumers use URLs starting with \"wss://\" followed by the fully qualified domain name of the Cumulocity environment or tenant, followed by a fixed URL path and a query string.\n\nThe fixed URL path is <kbd>/notification2/consumer/</kbd> and there are only two query string arguments:\n\n* `token` (required). Its value must be a valid token in the form of a JWT token string as returned by a create token request to the [Tokens methods](#tag/Tokens) of the Notifications 2.0 API. Including the token as a query string parameter avoids having to set an HTTP header which can be an issue for some WebSocket clients or proxies.\n\n* `consumer` (optional). Its value is a non blank unique name for the consumer.\n\nIn summary, the URLs used by consumers follow the following patterns:\n\n```\nwss://your.cumulocity.environment.fullqualifieddomainname/notification2/consumer/?token=yourJwtTokenRequestedFromNotification2TokenService\n```\n\nor\n\n```\nwss://your.cumulocity.environment.fullqualifieddomainname/notification2/consumer/?token=yourJwtTokenRequestedFromNotification2TokenService&consumer=aUniqueNameForThisConsumer\n```\n\n## WebSocket timeouts\n\nThere is a timeout of 5 minutes set on idle WebSocket connections after which the connection will be closed by the server side. Therefore the consumer must be prepared to handle closed connections which is required for fault-tolerant operation in any case. All consuming microservices or applications should handle the WebSocket being closed and re-connect as necessary. Alternatively, if you would like to keep the connection from being closed due to idle timeout, implement a ping-pong handler in the WebSocket consumer. For example, you can implement this mechanism in Jetty by following [Jetty Programming Guide > Client Libraries > WebSocket Client > WebSocket Session > Sending Ping/Pong](https://eclipse.dev/jetty/documentation/jetty-11/programming-guide/index.html#pg-websocket-session-ping). A few libraries also provide built-in support for keeping the connection open. [Java-WebSocket](https://github.com/TooTallNate/Java-WebSocket), for example, does this by sending the ping requests to the server every minute by default.\n\n\n## Notification acknowledgements\n\nThe WebSocket service sends a sequence of UTF-8 encoded textual notification messages to the consumer.\nEach notification message includes headers and a data payload.\nHeaders occur first in the message and are separated by new line characters. The data payload is last in the message,\nseparated from the last header by 2 new line characters, showing one blank line occurs between the last header and the payload.\nThe first header in the message is always the acknowledgement header.\n\nWhen the client has finished processing a notification message,\nit must send that message's acknowledgement header back to the server on the same connection the message was received on.\nSending the acknowledgement tells the server that the consumer has successfully received and processed that message,\nallowing the server to forget the message.\nEach acknowledgement is unique to a particular notification and consumer. Note that batch and cumulative acknowledgements are not supported.\n\nIf too many of a consumer's notifications (1000 by default) remain unacknowledged,\nthe flow of notification messages to that consumer will stop until some of its unacknowledged messages are acknowledged.\nIt is therefore best practise to process and acknowledge them quickly, to minimise the potential for a connection interruption causing a need to redeliver them.\nAn acknowledgement should not be sent until its notification has been successfully processed.\nOtherwise, for example, if the client crashed during or before such processing,\nthe message may be lost as acknowledged messages are not (usually) resent by the server.\n\nThe hello-world-notification-microservice example in the [cumulocity-examples repository](https://github.com/Cumulocity-IoT/cumulocity-examples/tree/develop/hello-world-notification-microservice)\nshows an example of how to send the acknowledgement back to the server in a self-contained WebSocket text message.\nIt should be sent without quotation marks, as it is not a JSON message, and without any trailing new-line characters.\n\n## Notification message header and content\n\nA notification (transmitted service to client) consists of a header and a body (similar to an HTTP request).\nThe header is one or more (in practice at least 3) lines of text, separated by a `\\n` (newline) character.\nThe end of the header is demarcated by a double new line `\\n\\n`.\n\nThe notification body follows the header.\nThis also consists of UTF text - for example a JSON document.\nIf the notification is binary data or includes binary data then it will be [Base64 encoded](https://en.wikipedia.org/wiki/Base64).\n\nThe header lines for a notification are as follows (separated by `\\n` newlines):\n\n* Required message identifier for message acknowledgement. This opaque value is an encoded binary 64 bit value. After the consumer has finished processing a notification, it must send this header back to the server to [acknowledge the notification](#section/Consumer-protocol/Notification-acknowledgements).\n\n* The [Notification description header](#notification-description-header) on the second header line. This is a string describing what type of notification this is and its source. Measurements (\"measurements\"), events (\"events\") and alarms (\"alarms\") are examples of notifications, as are inventory creates, updates and deletes (\"managedObjects\"). There is a direct correspondence with realtime notifications which features similar notification descriptions. These are not enumerated here and are expected to increase in number in the future. For REST API notifications, they follow a three-part format, separated by \"/\". For more details on notification descriptions see [Notification description header](#notification-description-header).\n\n* An action string is the third header. Examples are CREATE, UPDATE and DELETE. More actions may be added in the future. Together with the notification type they describe the logical event that generated the notification, such as a CREATE of an alarm or measurement.\n\nDepending on the second header there may be further headers to follow but currently notifications only use the above three.\nIn order to be future proof and forward compatible, we encourage consumer code to cope with more headers by parsing them out but ignoring them.\n\nSee the *hello-world-notification-microservice* example in the [cumulocity-examples repository](https://github.com/Cumulocity-IoT/cumulocity-examples/tree/develop/hello-world-notification-microservice) on how to do this.\n\nAfter the headers, the notification body follows as UTF-8 text. This is typically a JSON document.\n\n### Notification description header\n\nThe second header line is the notification description string in the form of a `/`-separated path. For API notifications descriptions have three parts: tenantId, type and sourceId.\n\n* tenantId - this the identifier for the tenant under which the notification was generated.\n\n* type - the platform type of notification generated. For example, event, measurement, alarm or managed object.\n\n* sourceId - the identifier of the \"source\" object that generated or is the subject of the notification. Source is a very loose term here, much as in \"event sourcing\" but generally indicates which managed object the notification is about.\n\nSome examples are provided in [Traces](#section/Traces) and backwards compatibility to real-time notifications is provided for.\n\n## Dealing with notification duplication\n\nWhen a WebSocket connection is lost, whether that is due to deliberate connection closure or connection failure,\nmessages that were received but not successfully acknowledged before the connection loss are sent to the consumer again when it reconnects.\nThis can result in duplicate messages being received by the consumer.\nThose duplicates can sometimes include acknowledged messages as these may be on-the-wire from the consumer to the server when the connection is lost.\n\nNotification messages do not contain any specific unique identifiers to aid in de-duplication.\nTherefore, any de-duplication of messages must be done by the consumer based upon the [Notification description header](#notification-description-header) and payload.\nNote that the acknowledgement header is not guaranteed to be unique across consumer reconnections, which is when duplicates are most likely.\n\nSome events are easy to de-duplicate, such as inventory events where a unique source object is first created and then deleted.\nIt will often be possible to use the notification message headers to determine these cases.\nHowever, inventory updates or logically sequenced events such as alarms and measurements will typically require application-specific understanding of the payload fields.\nIdeally the payload would include a field specifically for that purpose. If it is not possible to include such a field in the payload,\nthen other existing fields will have to be used, maybe on a best effort basis.\n\nA specific field in the payload would typically be a [UUID](https://en.wikipedia.org/wiki/Universally_unique_identifier) or increasing sequence number.\nThe latter may aid the efficiency of de-duplication by using ordering,\nthough the sequence numbers will be unrelated across publishers, typically IoT devices, when the messages are originated from more than one publisher.\nAny messages received from the same publisher with a lower sequence number than the last one processed from that publisher can be quickly discarded,\nassuming the sequence has not rolled over. It is also easier for a human to understand that the ordering is correct and all messages are present.\n\n# Traces\n\nThe following is a sample of a trace of messages. The dashed lines are not a part of the message, they are a visual aid to separate the messages in the trace.\n```\n------------------------\nCJ1eEAAgADAB\n/tenant-a170/managedobjects/111\nCREATE\n\n{\"additionParents\":{\"references\":[],\"self\":\"http://cumulocity.default.svc.cluster.local/inventory/managedObjects/111/additionParents\"},\"owner\":\"admin\",\"childDevices\":{\"references\":[],\"self\":\"http://cumulocity.default.svc.cluster.local/inventory/managedObjects/111/childDevices\"},\"childAssets\":{\"references\":[],\"self\":\"http://cumulocity.default.svc.cluster.local/inventory/managedObjects/111/childAssets\"},\"creationTime\":\"2021-09-03T12:28:58.692Z\",\"lastUpdated\":\"2021-09-03T12:28:58.692Z\",\"childAdditions\":{\"references\":[],\"self\":\"http://cumulocity.default.svc.cluster.local/inventory/managedObjects/111/childAdditions\"},\"name\":\"a switch\",\"assetParents\":{\"references\":[],\"self\":\"http://cumulocity.default.svc.cluster.local/inventory/managedObjects/111/assetParents\"},\"deviceParents\":{\"references\":[],\"self\":\"http://cumulocity.default.svc.cluster.local/inventory/managedObjects/111/deviceParents\"},\"self\":\"http://cumulocity.default.svc.cluster.local/inventory/managedObjects/111\",\"id\":\"111\",\"com_cumulocity_model_BinarySwitch\":{\"state\":\"ON\"}}\n------------------------\nCKReEAAgADAB\n/tenant-a170/measurements/111\nCREATE\n\n{\"self\":\"http://cumulocity.default.svc.cluster.local/measurement/measurements/117\",\"time\":\"2021-09-03T12:29:01.664Z\",\"id\":\"117\",\"source\":{\"self\":\"http://cumulocity.default.svc.cluster.local/inventory/managedObjects/111\",\"id\":\"111\"},\"type\":\"c8y_TenantUsageStatisticsMeasurement\",\"resourcesUsage\":{\"memory\":0,\"cpu\":0,\"usedBy\":[]}}\n------------------------\nCI9eEAAgADAB\n/tenant-a170/events/111\nCREATE\n\n{\"creationTime\":\"2021-09-03T12:29:01.932Z\",\"source\":{\"name\":\"a switch\",\"self\":\"http://cumulocity.default.svc.cluster.local/inventory/managedObjects/111\",\"id\":\"111\"},\"type\":\"com_cumulocity_model_DoorSensorEvent\",\"self\":\"http://cumulocity.default.svc.cluster.local/event/events/118\",\"time\":\"2021-09-03T12:29:01.664Z\",\"id\":\"118\",\"text\":\"Door sensor was triggered\"}\n------------------------\nCAAQACAAMAE=\n/tenant-a170/eventsWithChildren/111\nCREATE\n\n{\"creationTime\":\"2021-09-03T12:29:01.932Z\",\"source\":{\"name\":\"a switch\",\"self\":\"http://cumulocity.default.svc.cluster.local/inventory/managedObjects/111\",\"id\":\"111\"},\"type\":\"com_cumulocity_model_DoorSensorEvent\",\"self\":\"http://cumulocity.default.svc.cluster.local/event/events/118\",\"time\":\"2021-09-03T12:29:01.664Z\",\"id\":\"118\",\"text\":\"Door sensor was triggered\"}\n------------------------\nCLJeEAAgADAB\n/tenant-a170/managedobjects/111\nUPDATE\n\n{\"additionParents\":{\"references\":[],\"self\":\"http://cumulocity.default.svc.cluster.local/inventory/managedObjects/111/additionParents\"},\"owner\":\"admin\",\"childDevices\":{\"references\":[],\"self\":\"http://cumulocity.default.svc.cluster.local/inventory/managedObjects/111/childDevices\"},\"childAssets\":{\"references\":[],\"self\":\"http://cumulocity.default.svc.cluster.local/inventory/managedObjects/111/childAssets\"},\"creationTime\":\"2021-09-03T12:28:58.692Z\",\"lastUpdated\":\"2021-09-03T12:28:58.692Z\",\"childAdditions\":{\"references\":[],\"self\":\"http://cumulocity.default.svc.cluster.local/inventory/managedObjects/111/childAdditions\"},\"name\":\"a switch\",\"assetParents\":{\"references\":[],\"self\":\"http://cumulocity.default.svc.cluster.local/inventory/managedObjects/111/assetParents\"},\"deviceParents\":{\"references\":[],\"self\":\"http://cumulocity.default.svc.cluster.local/inventory/managedObjects/111/deviceParents\"},\"self\":\"http://cumulocity.default.svc.cluster.local/inventory/managedObjects/111\",\"id\":\"111\",\"c8y_ActiveAlarmsStatus\":{\"major\":1},\"com_cumulocity_model_BinarySwitch\":{\"state\":\"ON\"}}\n------------------------\nCMFeEAAgADAB\n/tenant-a170/alarms/111\nCREATE\n\n{\"severity\":\"MAJOR\",\"creationTime\":\"2021-09-03T12:29:02.092Z\",\"count\":1,\"history\":{\"auditRecords\":[],\"self\":\"http://cumulocity.default.svc.cluster.local/audit/auditRecords\"},\"source\":{\"name\":\"a switch\",\"self\":\"http://cumulocity.default.svc.cluster.local/inventory/managedObjects/111\",\"id\":\"111\"},\"type\":\"com_cumulocity_events_TamperEvent\",\"self\":\"http://cumulocity.default.svc.cluster.local/alarm/alarms/119\",\"time\":\"2021-09-03T12:29:01.664Z\",\"id\":\"119\",\"text\":\"Tamper sensor triggered\",\"status\":\"ACTIVE\",\"com_mycorp_MyProp\":{\"key1\":\"value1\"}}\n------------------------\nCLxdEBQgADAB\n/tenant-a170/alarms/111\nCREATE\n\n{\"severity\":\"MAJOR\",\"creationTime\":\"2021-09-03T12:29:02.092Z\",\"count\":1,\"history\":{\"auditRecords\":[],\"self\":\"http://cumulocity.default.svc.cluster.local/audit/auditRecords\"},\"source\":{\"name\":\"a switch\",\"self\":\"http://cumulocity.default.svc.cluster.local/inventory/managedObjects/111\",\"id\":\"111\"},\"type\":\"com_cumulocity_events_TamperEvent\",\"self\":\"http://cumulocity.default.svc.cluster.local/alarm/alarms/119\",\"time\":\"2021-09-03T12:29:01.664Z\",\"id\":\"119\",\"text\":\"Tamper sensor triggered\",\"status\":\"ACTIVE\",\"com_mycorp_MyProp\":{\"key1\":\"value1\"}}\n------------------------\nCMJdEAAgADAB\n/tenant-a170/alarmsWithChildren/111\nCREATE\n\n{\"severity\":\"MAJOR\",\"creationTime\":\"2021-09-03T12:29:02.092Z\",\"count\":1,\"history\":{\"auditRecords\":[],\"self\":\"http://cumulocity.default.svc.cluster.local/audit/auditRecords\"},\"source\":{\"name\":\"a switch\",\"self\":\"http://cumulocity.default.svc.cluster.local/inventory/managedObjects/111\",\"id\":\"111\"},\"type\":\"com_cumulocity_events_TamperEvent\",\"self\":\"http://cumulocity.default.svc.cluster.local/alarm/alarms/119\",\"time\":\"2021-09-03T12:29:01.664Z\",\"id\":\"119\",\"text\":\"Tamper sensor triggered\",\"status\":\"ACTIVE\",\"com_mycorp_MyProp\":{\"key1\":\"value1\"}}\n```\n"
|
|
1759
1759
|
},
|
|
1760
1760
|
{
|
|
1761
1761
|
"name": "Notification 2.0 API",
|
|
@@ -22962,7 +22962,7 @@ const specs = Object.freeze([
|
|
|
22962
22962
|
},
|
|
22963
22963
|
"subscription": {
|
|
22964
22964
|
"type": "string",
|
|
22965
|
-
"description": "The subscription name
|
|
22965
|
+
"description": "The subscription name.\nThe name identifies the Messaging Service topic that messages for this subscription will be published to.\nSeveral subscriptions can share the same name, which means that the same topic will receive messages for all those subscriptions.\nSee [topics and subscriptions](#section/Overview/Topics-and-subscriptions) for more details.\n",
|
|
22966
22966
|
"pattern": "^[a-zA-Z0-9]+$",
|
|
22967
22967
|
"minLength": 1
|
|
22968
22968
|
},
|
|
@@ -91804,7 +91804,7 @@ const prompt = {
|
|
|
91804
91804
|
//#endregion
|
|
91805
91805
|
//#region src/prompts/codemode.ts
|
|
91806
91806
|
function getRuntimeSection() {
|
|
91807
|
-
return c8yMcpServer.ctx.custom?.env === "cli" ? "## CLI Runtime\nUse `
|
|
91807
|
+
return c8yMcpServer.ctx.custom?.env === "cli" ? "## CLI Runtime\nUse `status` to see the active tenant, stored tenant URLs, and the specs visible to query right now. Use `set-active-tenant` to connect to a tenant. Once set, query and execute use that tenant automatically. When no tenant is active, query falls back to all bundled OpenAPI snapshots for reference and execute is unavailable. If a microservice was just (un)subscribed in the tenant, call `status` with `refresh: true` to bust the 30-minute discovery cache." : "## Server Runtime\nThis deployed MCP server uses the current tenant and the service user attached to this MCP connection. Do not pass tenant-specific credentials or tenant URLs yourself.";
|
|
91808
91808
|
}
|
|
91809
91809
|
function getOpenApiSection() {
|
|
91810
91810
|
return `## OpenAPI Specs\nThe query tool exposes a bundled Cumulocity core snapshot via \`coreSpec\`, and any microservice APIs (bundled or live-discovered) available on the tenant via \`serviceSpecs\`.`;
|
|
@@ -165825,7 +165825,8 @@ const EXECUTE_ENTRY_PATH = "/codemode-execute.mjs";
|
|
|
165825
165825
|
const BLOCKED_REQUEST_PREFIX = "Request blocked by MCP connection policy.";
|
|
165826
165826
|
const SANDBOX_LIMITS = {
|
|
165827
165827
|
memoryMb: 128,
|
|
165828
|
-
cpuTimeMs: 5e4
|
|
165828
|
+
cpuTimeMs: 5e4,
|
|
165829
|
+
maxBridgeCalls: 200
|
|
165829
165830
|
};
|
|
165830
165831
|
let sandboxPromise = null;
|
|
165831
165832
|
async function getSandbox() {
|
|
@@ -166023,13 +166024,13 @@ function getOpenApiNote() {
|
|
|
166023
166024
|
}
|
|
166024
166025
|
function getQuerySafetyPreface(env) {
|
|
166025
166026
|
if (env === "server") return "Searches the bundled and discovered OpenAPI specs available to the current connection.";
|
|
166026
|
-
return "**Read first.** The active tenant is global to this CLI session and can be flipped between calls by `set-active-tenant`. Every result ends with a footer line naming the active tenant (or noting there is none) so you can verify which tenant the result reflects before acting on it. If the footer says \"no active tenant\" you are looking at bundled reference snapshots — call `
|
|
166027
|
+
return "**Read first.** The active tenant is global to this CLI session and can be flipped between calls by `set-active-tenant`. Every result ends with a footer line naming the active tenant (or noting there is none) so you can verify which tenant the result reflects before acting on it. If the footer says \"no active tenant\" you are looking at bundled reference snapshots — call `status` to see stored credentials and `set-active-tenant` to connect before relying on the result.";
|
|
166027
166028
|
}
|
|
166028
166029
|
function getExecuteSafetyPreface(env) {
|
|
166029
166030
|
const sharedFooter = "An endpoint visible in `query` may still return 404 from `execute` when the service is not actually installed on the current tenant.";
|
|
166030
166031
|
if (env === "server") return sharedFooter;
|
|
166031
166032
|
return [
|
|
166032
|
-
"**Read first.** Every result starts with an `Executed against tenant: <url>` marker line followed by a blank line. Verify it matches the tenant you intend to mutate before reporting the result. The active tenant is global to this CLI session and can be flipped between calls by `set-active-tenant`. If no tenant is active `execute` fails with a missing-auth error — call `
|
|
166033
|
+
"**Read first.** Every result starts with an `Executed against tenant: <url>` marker line followed by a blank line. Verify it matches the tenant you intend to mutate before reporting the result. The active tenant is global to this CLI session and can be flipped between calls by `set-active-tenant`. If no tenant is active `execute` fails with a missing-auth error — call `status` and `set-active-tenant` to connect first.",
|
|
166033
166034
|
"",
|
|
166034
166035
|
sharedFooter
|
|
166035
166036
|
].join("\n");
|
|
@@ -166155,6 +166156,47 @@ function createTools(env) {
|
|
|
166155
166156
|
return [createQueryTool(env), createExecuteTool(env)];
|
|
166156
166157
|
}
|
|
166157
166158
|
//#endregion
|
|
166159
|
+
//#region src/cli/active-tenant.ts
|
|
166160
|
+
const CONFIG_DIR = join(homedir(), ".config", "mc8yp");
|
|
166161
|
+
const CONFIG_FILE = join(CONFIG_DIR, "active-tenant.json");
|
|
166162
|
+
/**
|
|
166163
|
+
* Persist the active tenant URL to disk.
|
|
166164
|
+
* Creates the config directory if it does not exist.
|
|
166165
|
+
* @param tenantUrl
|
|
166166
|
+
*/
|
|
166167
|
+
function writeActiveTenant(tenantUrl) {
|
|
166168
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
166169
|
+
writeFileSync(CONFIG_FILE, JSON.stringify({ tenantUrl }), "utf8");
|
|
166170
|
+
}
|
|
166171
|
+
/**
|
|
166172
|
+
* Read the active tenant URL from disk.
|
|
166173
|
+
* Returns null when the file is missing, malformed, has the wrong shape,
|
|
166174
|
+
* or holds an explicit `{ tenantUrl: null }` marker written by clearActiveTenant.
|
|
166175
|
+
*/
|
|
166176
|
+
function readActiveTenantUrl() {
|
|
166177
|
+
try {
|
|
166178
|
+
const raw = JSON.parse(readFileSync(CONFIG_FILE, "utf8"));
|
|
166179
|
+
if (raw && typeof raw === "object" && "tenantUrl" in raw) {
|
|
166180
|
+
const value = raw.tenantUrl;
|
|
166181
|
+
if (typeof value === "string") return value;
|
|
166182
|
+
}
|
|
166183
|
+
return null;
|
|
166184
|
+
} catch {
|
|
166185
|
+
return null;
|
|
166186
|
+
}
|
|
166187
|
+
}
|
|
166188
|
+
/**
|
|
166189
|
+
* Persist an explicit "no active tenant" marker (`{ tenantUrl: null }`).
|
|
166190
|
+
* Used by drift recovery and the explicit reset path. Keeping the file
|
|
166191
|
+
* (instead of unlinking) makes the state easy to inspect and avoids any
|
|
166192
|
+
* confusion between "no active tenant ever set" and "active tenant
|
|
166193
|
+
* intentionally cleared".
|
|
166194
|
+
*/
|
|
166195
|
+
function clearActiveTenant() {
|
|
166196
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
166197
|
+
writeFileSync(CONFIG_FILE, JSON.stringify({ tenantUrl: null }), "utf8");
|
|
166198
|
+
}
|
|
166199
|
+
//#endregion
|
|
166158
166200
|
//#region src/utils/api-discovery.ts
|
|
166159
166201
|
/**
|
|
166160
166202
|
* Live microservice API spec discovery.
|
|
@@ -166191,6 +166233,23 @@ function startDiscovery(tenantId, client) {
|
|
|
166191
166233
|
});
|
|
166192
166234
|
return promise;
|
|
166193
166235
|
}
|
|
166236
|
+
/**
|
|
166237
|
+
* Bust the cache for a tenant and immediately start fresh discovery.
|
|
166238
|
+
* @param tenantId - Cumulocity tenant ID for the cache key
|
|
166239
|
+
* @param client - Configured Cumulocity client for the fresh discovery run
|
|
166240
|
+
*/
|
|
166241
|
+
function refreshApiSpecs(tenantId, client) {
|
|
166242
|
+
cache.delete(tenantId);
|
|
166243
|
+
return startDiscovery(tenantId, client);
|
|
166244
|
+
}
|
|
166245
|
+
/**
|
|
166246
|
+
* Peek the cache without triggering discovery. Returns the in-flight or
|
|
166247
|
+
* resolved promise for this tenant, or undefined when no entry exists.
|
|
166248
|
+
* @param tenantId - Cumulocity tenant ID for the cache key
|
|
166249
|
+
*/
|
|
166250
|
+
function getCachedDiscovery(tenantId) {
|
|
166251
|
+
return cache.get(tenantId);
|
|
166252
|
+
}
|
|
166194
166253
|
function rewriteDiscoveredSpecPaths(spec, servicePrefix) {
|
|
166195
166254
|
const paths = spec.paths;
|
|
166196
166255
|
if (paths && typeof paths === "object") {
|
|
@@ -174145,47 +174204,6 @@ async function setCliTenantContext(tenantUrl) {
|
|
|
174145
174204
|
return _context;
|
|
174146
174205
|
}
|
|
174147
174206
|
//#endregion
|
|
174148
|
-
//#region src/cli/active-tenant.ts
|
|
174149
|
-
const CONFIG_DIR = join(homedir(), ".config", "mc8yp");
|
|
174150
|
-
const CONFIG_FILE = join(CONFIG_DIR, "active-tenant.json");
|
|
174151
|
-
/**
|
|
174152
|
-
* Persist the active tenant URL to disk.
|
|
174153
|
-
* Creates the config directory if it does not exist.
|
|
174154
|
-
* @param tenantUrl
|
|
174155
|
-
*/
|
|
174156
|
-
function writeActiveTenant(tenantUrl) {
|
|
174157
|
-
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
174158
|
-
writeFileSync(CONFIG_FILE, JSON.stringify({ tenantUrl }), "utf8");
|
|
174159
|
-
}
|
|
174160
|
-
/**
|
|
174161
|
-
* Read the active tenant URL from disk.
|
|
174162
|
-
* Returns null when the file is missing, malformed, has the wrong shape,
|
|
174163
|
-
* or holds an explicit `{ tenantUrl: null }` marker written by clearActiveTenant.
|
|
174164
|
-
*/
|
|
174165
|
-
function readActiveTenantUrl() {
|
|
174166
|
-
try {
|
|
174167
|
-
const raw = JSON.parse(readFileSync(CONFIG_FILE, "utf8"));
|
|
174168
|
-
if (raw && typeof raw === "object" && "tenantUrl" in raw) {
|
|
174169
|
-
const value = raw.tenantUrl;
|
|
174170
|
-
if (typeof value === "string") return value;
|
|
174171
|
-
}
|
|
174172
|
-
return null;
|
|
174173
|
-
} catch {
|
|
174174
|
-
return null;
|
|
174175
|
-
}
|
|
174176
|
-
}
|
|
174177
|
-
/**
|
|
174178
|
-
* Persist an explicit "no active tenant" marker (`{ tenantUrl: null }`).
|
|
174179
|
-
* Used by drift recovery and the explicit reset path. Keeping the file
|
|
174180
|
-
* (instead of unlinking) makes the state easy to inspect and avoids any
|
|
174181
|
-
* confusion between "no active tenant ever set" and "active tenant
|
|
174182
|
-
* intentionally cleared".
|
|
174183
|
-
*/
|
|
174184
|
-
function clearActiveTenant() {
|
|
174185
|
-
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
174186
|
-
writeFileSync(CONFIG_FILE, JSON.stringify({ tenantUrl: null }), "utf8");
|
|
174187
|
-
}
|
|
174188
|
-
//#endregion
|
|
174189
174207
|
//#region src/tools/active-tenant.ts
|
|
174190
174208
|
/**
|
|
174191
174209
|
* Clear the active tenant everywhere it is recorded: persistence file,
|
|
@@ -174194,7 +174212,7 @@ function clearActiveTenant() {
|
|
|
174194
174212
|
* the no-tenant state — query falls back to bundled-only specs, execute
|
|
174195
174213
|
* errors loudly on missing auth.
|
|
174196
174214
|
*
|
|
174197
|
-
* Exported so the drift-recovery paths (
|
|
174215
|
+
* Exported so the drift-recovery paths (status, CLI startup) can reuse
|
|
174198
174216
|
* the same teardown the explicit reset uses.
|
|
174199
174217
|
*/
|
|
174200
174218
|
function resetActiveTenant() {
|
|
@@ -174210,13 +174228,13 @@ function createSetActiveTenantTool() {
|
|
|
174210
174228
|
return defineTool({
|
|
174211
174229
|
name: "set-active-tenant",
|
|
174212
174230
|
title: "Set Active Tenant",
|
|
174213
|
-
description: "Set the Cumulocity tenant for this CLI session, or pass tenantUrl: null to clear the active tenant. The tenantUrl must match one returned by
|
|
174214
|
-
schema: /* @__PURE__ */ object({ tenantUrl: /* @__PURE__ */ nullable(/* @__PURE__ */ pipe(/* @__PURE__ */ string(), /* @__PURE__ */ url("Must be a valid URL, e.g. https://mytenant.cumulocity.com"), /* @__PURE__ */ description("Base URL of the Cumulocity tenant — must be present in
|
|
174231
|
+
description: "Set the Cumulocity tenant for this CLI session, or pass tenantUrl: null to clear the active tenant. The tenantUrl must match one returned by the status tool. The selection is persisted across sessions so you only need to call this once (or when switching tenants). Clearing falls back to bundled-only browsing — query still works but execute is unavailable until a tenant is set again.",
|
|
174232
|
+
schema: /* @__PURE__ */ object({ tenantUrl: /* @__PURE__ */ nullable(/* @__PURE__ */ pipe(/* @__PURE__ */ string(), /* @__PURE__ */ url("Must be a valid URL, e.g. https://mytenant.cumulocity.com"), /* @__PURE__ */ description("Base URL of the Cumulocity tenant — must be present in the status tool output. Pass null to clear the active tenant."))) })
|
|
174215
174233
|
}, async (input) => {
|
|
174216
174234
|
try {
|
|
174217
174235
|
if (input.tenantUrl === null) {
|
|
174218
174236
|
resetActiveTenant();
|
|
174219
|
-
return tool.text("Active tenant cleared. Query now falls back to all bundled OpenAPI snapshots; execute is unavailable until you set a tenant. Call set-active-tenant with a tenantUrl from
|
|
174237
|
+
return tool.text("Active tenant cleared. Query now falls back to all bundled OpenAPI snapshots; execute is unavailable until you set a tenant. Call set-active-tenant with a tenantUrl from the status tool to reconnect.");
|
|
174220
174238
|
}
|
|
174221
174239
|
const storedCreds = await globalThis._getStoredC8yAuth();
|
|
174222
174240
|
if (!storedCreds.find((c) => c.tenantUrl === input.tenantUrl)) {
|
|
@@ -174240,34 +174258,117 @@ function createSetActiveTenantTool() {
|
|
|
174240
174258
|
});
|
|
174241
174259
|
}
|
|
174242
174260
|
//#endregion
|
|
174243
|
-
//#region src/tools/
|
|
174244
|
-
|
|
174261
|
+
//#region src/tools/status.ts
|
|
174262
|
+
const STATUS_TOOL_DESCRIPTION = "Show the current CLI status: stored tenant credentials, which tenant query and execute will hit, and the specs visible to query right now. If no tenant is active, query falls back to all bundled OpenAPI snapshots and execute is unavailable — set-active-tenant must be called first. This tool also self-heals: if the active tenant has lost its stored credentials it is automatically reset before the status is reported.\n\nPass `refresh: true` to force a fresh API spec discovery against the active tenant. Use this after subscribing or unsubscribing a microservice in the tenant — otherwise discovered specs stay cached for 30 minutes. If no tenant is active, `refresh: true` is a noop.";
|
|
174263
|
+
function createStatusTool() {
|
|
174245
174264
|
return defineTool({
|
|
174246
|
-
name: "
|
|
174247
|
-
|
|
174248
|
-
|
|
174249
|
-
|
|
174250
|
-
|
|
174251
|
-
|
|
174252
|
-
if (active && !creds.some((c) => c.tenantUrl === active.tenantUrl)) {
|
|
174253
|
-
const cleared = active.tenantUrl;
|
|
174254
|
-
resetActiveTenant();
|
|
174255
|
-
active = null;
|
|
174256
|
-
driftRecoveryNotice = `Active tenant ${cleared} was cleared automatically because no credentials are stored for it. Query now falls back to all bundled OpenAPI snapshots; execute is unavailable until you set a tenant.`;
|
|
174257
|
-
}
|
|
174258
|
-
const sections = [];
|
|
174259
|
-
if (driftRecoveryNotice) sections.push(driftRecoveryNotice);
|
|
174260
|
-
if (active) sections.push(`Active tenant: ${active.tenantUrl}`);
|
|
174261
|
-
else sections.push("Active tenant: (none) — query falls back to all bundled OpenAPI snapshots; execute is unavailable until set-active-tenant is called. Visibility in the bundled-only mode does NOT guarantee any service is installed on any tenant.");
|
|
174262
|
-
if (creds.length === 0) sections.push("Stored credentials: (none). Use `creds add` from the shell to register a tenant before calling set-active-tenant.");
|
|
174263
|
-
else {
|
|
174264
|
-
const lines = creds.map((c) => `- ${c.tenantUrl} (tenantId: ${c.tenantId})`).join("\n");
|
|
174265
|
-
sections.push(`Stored credentials:\n${lines}`);
|
|
174266
|
-
}
|
|
174267
|
-
if (!active && creds.length > 0) sections.push("Next step: call set-active-tenant with one of the tenant URLs above before using query or execute.");
|
|
174268
|
-
return tool.text(sections.join("\n\n"));
|
|
174265
|
+
name: "status",
|
|
174266
|
+
title: "mc8yp Status",
|
|
174267
|
+
description: STATUS_TOOL_DESCRIPTION,
|
|
174268
|
+
schema: /* @__PURE__ */ object({ refresh: /* @__PURE__ */ optional(/* @__PURE__ */ pipe(/* @__PURE__ */ boolean(), /* @__PURE__ */ description("When true, bust the API discovery cache for the active tenant and run a fresh discovery before reporting. Noop when no tenant is active.")), false) })
|
|
174269
|
+
}, async (input) => {
|
|
174270
|
+
return tool.text(await buildCliStatus(input.refresh === true));
|
|
174269
174271
|
});
|
|
174270
174272
|
}
|
|
174273
|
+
async function buildCliStatus(refresh) {
|
|
174274
|
+
const creds = await globalThis._getStoredC8yAuth();
|
|
174275
|
+
let active = getCliTenantContext();
|
|
174276
|
+
const sections = [];
|
|
174277
|
+
if (active && !creds.some((c) => c.tenantUrl === active.tenantUrl)) {
|
|
174278
|
+
const cleared = active.tenantUrl;
|
|
174279
|
+
resetActiveTenant();
|
|
174280
|
+
active = null;
|
|
174281
|
+
sections.push(`Active tenant ${cleared} was cleared automatically because no credentials are stored for it. Query now falls back to all bundled OpenAPI snapshots; execute is unavailable until you set a tenant.`);
|
|
174282
|
+
}
|
|
174283
|
+
if (refresh) if (!active) sections.push("Refresh requested but no tenant is active — nothing to refresh. Call set-active-tenant first.");
|
|
174284
|
+
else {
|
|
174285
|
+
sections.push(await refreshCliActiveTenant(active.tenantUrl));
|
|
174286
|
+
active = getCliTenantContext();
|
|
174287
|
+
}
|
|
174288
|
+
if (active) sections.push(`Active tenant: ${active.tenantUrl}`);
|
|
174289
|
+
else sections.push("Active tenant: (none) — query falls back to all bundled OpenAPI snapshots; execute is unavailable until set-active-tenant is called. Visibility in the bundled-only mode does NOT guarantee any service is installed on any tenant.");
|
|
174290
|
+
if (creds.length === 0) sections.push("Stored credentials: (none). Use `creds add` from the shell to register a tenant before calling set-active-tenant.");
|
|
174291
|
+
else {
|
|
174292
|
+
const lines = creds.map((c) => `- ${c.tenantUrl} (tenantId: ${c.tenantId})`).join("\n");
|
|
174293
|
+
sections.push(`Stored credentials:\n${lines}`);
|
|
174294
|
+
}
|
|
174295
|
+
let activeTenantId;
|
|
174296
|
+
if (active) try {
|
|
174297
|
+
activeTenantId = (await globalThis._getCredentialsByTenantUrl(active.tenantUrl)).tenantId;
|
|
174298
|
+
} catch {
|
|
174299
|
+
activeTenantId = void 0;
|
|
174300
|
+
}
|
|
174301
|
+
sections.push(await buildVisibleSpecsSection(activeTenantId));
|
|
174302
|
+
if (!active && creds.length > 0) sections.push("Next step: call set-active-tenant with one of the tenant URLs above before using query or execute.");
|
|
174303
|
+
return sections.join("\n\n");
|
|
174304
|
+
}
|
|
174305
|
+
/**
|
|
174306
|
+
* Trigger a fresh discovery for the CLI's active tenant and update the
|
|
174307
|
+
* in-memory specs everywhere they are read from. Returns a short text
|
|
174308
|
+
* suitable for inclusion in the status output.
|
|
174309
|
+
* @param tenantUrl - Base URL of the active Cumulocity tenant.
|
|
174310
|
+
*/
|
|
174311
|
+
async function refreshCliActiveTenant(tenantUrl) {
|
|
174312
|
+
try {
|
|
174313
|
+
const creds = await globalThis._getCredentialsByTenantUrl(tenantUrl);
|
|
174314
|
+
const client = new Client(new BasicAuth({
|
|
174315
|
+
tenant: creds.tenantId,
|
|
174316
|
+
user: creds.user,
|
|
174317
|
+
password: creds.password
|
|
174318
|
+
}), tenantUrl);
|
|
174319
|
+
const result = await refreshApiSpecs(creds.tenantId, client);
|
|
174320
|
+
const resolved = resolveSpecs(result.specs, result.installedContextPaths);
|
|
174321
|
+
const cliCtx = getCliTenantContext();
|
|
174322
|
+
if (cliCtx) cliCtx.specs = resolved;
|
|
174323
|
+
const custom = c8yMcpServer.ctx.custom;
|
|
174324
|
+
if (custom) custom.specs = resolved;
|
|
174325
|
+
return `Refreshed API discovery for ${tenantUrl}: ${result.specs.length} spec(s) downloaded, ${result.installedContextPaths.size} subscribed application(s).`;
|
|
174326
|
+
} catch (err) {
|
|
174327
|
+
return `Refresh failed for ${tenantUrl}: ${err instanceof Error ? err.message : String(err)}`;
|
|
174328
|
+
}
|
|
174329
|
+
}
|
|
174330
|
+
/**
|
|
174331
|
+
* Render the "Visible specs" block. Awaits the cached discovery promise
|
|
174332
|
+
* (if any) to enrich entries with app/spec labels; falls back to a bare
|
|
174333
|
+
* contextPath list when the cache is cold or the lookup fails.
|
|
174334
|
+
* @param tenantId - Tenant ID used as the discovery cache key for label enrichment.
|
|
174335
|
+
*/
|
|
174336
|
+
async function buildVisibleSpecsSection(tenantId) {
|
|
174337
|
+
const specs = c8yMcpServer.ctx.custom?.specs;
|
|
174338
|
+
if (!specs) return "Visible specs: (none) — no tenant context resolved.";
|
|
174339
|
+
const labels = await getDiscoveryLabels(tenantId);
|
|
174340
|
+
const lines = ["- core (Cumulocity core API, always present as `coreSpec`)"];
|
|
174341
|
+
const serviceKeys = Object.keys(specs.specs).sort();
|
|
174342
|
+
if (serviceKeys.length === 0) lines.push(" (no service specs available)");
|
|
174343
|
+
else for (const key of serviceKeys) {
|
|
174344
|
+
const meta = labels?.get(key);
|
|
174345
|
+
const label = meta ? ` — ${meta.appLabel}${meta.specLabel !== meta.appLabel ? ` / ${meta.specLabel}` : ""}` : "";
|
|
174346
|
+
lines.push(`- serviceSpecs.${key}${label}`);
|
|
174347
|
+
}
|
|
174348
|
+
return `Visible specs (use in query as \`coreSpec\` / \`serviceSpecs.<key>\`):\n${lines.join("\n")}`;
|
|
174349
|
+
}
|
|
174350
|
+
/**
|
|
174351
|
+
* Best-effort discovery metadata lookup. Returns undefined when the cache
|
|
174352
|
+
* has no entry for the tenant or when reading it throws — the caller
|
|
174353
|
+
* gracefully falls back to a label-less listing.
|
|
174354
|
+
* @param tenantId - Tenant ID used as the discovery cache key.
|
|
174355
|
+
*/
|
|
174356
|
+
async function getDiscoveryLabels(tenantId) {
|
|
174357
|
+
if (!tenantId) return void 0;
|
|
174358
|
+
const cached = getCachedDiscovery(tenantId);
|
|
174359
|
+
if (!cached) return void 0;
|
|
174360
|
+
try {
|
|
174361
|
+
const result = await cached;
|
|
174362
|
+
const map = /* @__PURE__ */ new Map();
|
|
174363
|
+
for (const s of result.specs) map.set(s.contextPath, {
|
|
174364
|
+
appLabel: s.appLabel,
|
|
174365
|
+
specLabel: s.specLabel
|
|
174366
|
+
});
|
|
174367
|
+
return map;
|
|
174368
|
+
} catch {
|
|
174369
|
+
return;
|
|
174370
|
+
}
|
|
174371
|
+
}
|
|
174271
174372
|
//#endregion
|
|
174272
174373
|
//#region src/server.ts
|
|
174273
174374
|
/**
|
|
@@ -174279,7 +174380,7 @@ function setupMcpServer(env) {
|
|
|
174279
174380
|
c8yMcpServer.prompts(createPrompts());
|
|
174280
174381
|
consola.info("Running in execution environment:", env);
|
|
174281
174382
|
if (env === "cli") {
|
|
174282
|
-
c8yMcpServer.tool(
|
|
174383
|
+
c8yMcpServer.tool(createStatusTool());
|
|
174283
174384
|
c8yMcpServer.tool(createSetActiveTenantTool());
|
|
174284
174385
|
}
|
|
174285
174386
|
}
|
|
@@ -174321,10 +174422,10 @@ async function getStoredC8yAuth() {
|
|
|
174321
174422
|
}
|
|
174322
174423
|
async function getCredentialsByTenantUrl(tenantUrl) {
|
|
174323
174424
|
const cleanedUrl = cleanTenantUrl(tenantUrl);
|
|
174324
|
-
|
|
174325
|
-
if (
|
|
174326
|
-
|
|
174327
|
-
return parseStoredUserC8yAuth(
|
|
174425
|
+
let entry = (await findCredentialsAsync(name, cleanedUrl))[0];
|
|
174426
|
+
if (!entry) entry = (await findCredentialsAsync(name)).find((e) => cleanTenantUrl(e.account) === cleanedUrl);
|
|
174427
|
+
if (!entry) throw new Error(`No stored credentials found for tenant URL: ${cleanedUrl}`);
|
|
174428
|
+
return parseStoredUserC8yAuth(entry.password, cleanedUrl);
|
|
174328
174429
|
}
|
|
174329
174430
|
async function setStoredC8yAuth(creds) {
|
|
174330
174431
|
const cleanedTenantUrl = cleanTenantUrl(creds.tenantUrl);
|
|
@@ -174347,14 +174448,16 @@ function cleanTenantUrl(url) {
|
|
|
174347
174448
|
}
|
|
174348
174449
|
async function deleteStoredC8yAuth(tenantUrl) {
|
|
174349
174450
|
const cleanedUrl = cleanTenantUrl(tenantUrl);
|
|
174350
|
-
if (!(await findCredentialsAsync(name)).some((entry) => entry.account === cleanedUrl)) return false;
|
|
174451
|
+
if (!(await findCredentialsAsync(name)).some((entry) => cleanTenantUrl(entry.account) === cleanedUrl)) return false;
|
|
174351
174452
|
const entry = new AsyncEntry(name, cleanedUrl);
|
|
174453
|
+
let deleteError;
|
|
174352
174454
|
try {
|
|
174353
174455
|
await entry.deletePassword();
|
|
174354
|
-
|
|
174355
|
-
|
|
174356
|
-
return false;
|
|
174456
|
+
} catch (err) {
|
|
174457
|
+
deleteError = err;
|
|
174357
174458
|
}
|
|
174459
|
+
if (!(await findCredentialsAsync(name)).some((e) => cleanTenantUrl(e.account) === cleanedUrl)) return true;
|
|
174460
|
+
throw new Error(`Keyring refused to delete the credentials for ${cleanedUrl}. The entry is present when listing but cannot be removed by target — this is typically a libsecret / WSL2 locked-collection issue. Unlock the login keyring (e.g. \`gnome-keyring-daemon --unlock --components=secrets\`) and try again, or remove the entry manually with \`secret-tool clear service ${name} account ${cleanedUrl}\`.`, deleteError instanceof Error ? { cause: deleteError } : void 0);
|
|
174358
174461
|
}
|
|
174359
174462
|
//#endregion
|
|
174360
174463
|
//#region src/cli/index.ts
|
|
@@ -174386,7 +174489,7 @@ runMain(defineCommand({
|
|
|
174386
174489
|
globalThis._getCredentialsByTenantUrl = getCredentialsByTenantUrl;
|
|
174387
174490
|
globalThis._getStoredC8yAuth = getStoredC8yAuth;
|
|
174388
174491
|
},
|
|
174389
|
-
subCommands: { creds: () => import("./creds-
|
|
174492
|
+
subCommands: { creds: () => import("./creds-PUpnfVX2.mjs").then((m) => m.default) },
|
|
174390
174493
|
run: async ({ args }) => {
|
|
174391
174494
|
const requested = (Array.isArray(args.spec) ? args.spec.at(-1) : args.spec) ?? getCoreOpenApiVersion();
|
|
174392
174495
|
const selected = specs.find((e) => e.version === requested);
|