# RESTful Adapter Service

### 1. Purpose of the document

This documentation contains an overview of the Visual Studio solution and descriptions of all REST service calls. Additional to all calls an example request and response as json objects are explained and displayed.

### 2. Visual Studio Solution for REST Service

Content of the solution:

<div align="left"><img src="/files/-M8zzNXilC86-LXEK74p" alt=""></div>

The VS solution consists of three projects and one configuration:

* **TivityPluginService**: WebAPI project, the actual plugin REST service
* **Tivity.Plugnis.Dummy**: a example adapter serves inside the WebAPI as fallback adapter
* **Tivity.Plugins.RestService.Test**: UnitTests for the REST Service project
* **docker-compose**: the docker configuration, it is possible to start the service&#x20;

  &#x20; in the container out of the service.

After you build the solution, the service starts and the homepage appears in the browser from the plugin service. The start page is an openAPI documentation like swagger.

![](/files/-M8zzcrNAtM8hBKTa7j7)

### 3. Plugin Service Project

This is a project created as a VS WebAPI project based on .NET core 2.1 and serves as the API interface for an adapter instance. This project is intended as example code for a REST Service which the TIVITY platform can communicate with. Of course, this service can also be used productively and a specially developed adapter connect and wrap. This adapter should be available as a Microsoft .NET assembly.

> ***NOTE***\
> It can also be used a special developed adapter (e.g. Java). This must be identical to the interface used in the example service.

<div align="left"><img src="/files/-M8zzv4Px3nn2xFVVxK9" alt=""></div>

The service basically consists of the API controllers and the wrapper class. The controllers are the interfaces to "outside" while the wrapper ensures the communication "inside", namely to the adapter.\
In addition, there are the usual Startup and program classes for the service, which are used for the entry point. Further configurations are stored in the Dockerfile and *appsettings.json*.

### **3.1 SourceController**

The SourceController is a important API controller for the communication with the SourceAdapter. There, the SourceAdapter interfaces (ISourceAdapter, ISource) are implemented. These interfaces provide the configuration and adapter instantiation as API calls.

The interface for the adapter is defined like this:

```csharp
    public interface ISourceAdapter
    {
        Task<SourceResult<AdapterDefinition>> Define(AdapterDefineRequest request);
        Task<SourceResult<ConfigurationStep>> Config(AdapterConfigLaunchRequest request);
        Task<SourceResult<ConfigurationStep>> Config(AdapterConfigHandleRequest request);
        Task<SourceResult<ISource>> Connect(AdapterConnectRequest request);
    }
    public interface ISource
    {
        Task<SourceResult<SourceCapabilityMap>> GetCapabilityMap(SourceCapabilityRequest request);
        TCapability GetCapability<TCapability>() where TCapability : ISourceCapability;
    }
```

The corresponding methods in the API Controller look very similar (simplified method body):

```csharp
public async Task<ActionResult<SourceResult<AdapterDefinition>>> Define(AdapterDefineRequest request)
{
    SourceResult<AdapterDefinition> result = await AdapterWrapper.Instance.Define(request);
    return result;
}

public Task<SourceResult<ISource>> Connect(AdapterConnectRequest request)
{
    return AdapterWrapper.Instance.Connect(request).Result;
}

public async Task<ActionResult<SourceResult<ConfigurationStep<ParameterStep>>>> Config()
{
    SourceResult<ConfigurationStep> result = await AdapterWrapper.Instance.Config(new AdapterConfigLaunchRequest());
   return new SourceResult<ConfigurationStep>(result.Value as ConfigurationStep<ParameterStep>); 
}

public async Task<ActionResult<SourceResult<ConfigurationStep>>> Config([FromBody] AdapterConfigHandleRequest value)
{
    SourceResult<ConfigurationStep> result = await AdapterWrapper.Instance.Config(value);
    return new SourceResult<ConfigurationStep>(result.Value as ConfigurationStep<ParameterStep>); 
}
public async Task<ActionResult<SourceResult<SourceCapabilityMap>>> GetCapability()
{
    return await AdapterWrapper.Instance.GetCapabilityMap(new SourceCapabilityRequest());
}
```

Except for Connect, all methods are asynchronous. And it is noticeable that another *ActionResult* type is given as a template. This is a standard Microsoft return type **`ActionResult<T>`** for asp.net core. and was just added for comfortable response generation. This type is not necessary for your own service. Otherwise the methods correspond to those of the interface *ISourceAdapter*.

## &#x20;Source / Connect

<mark style="color:green;">`POST`</mark> `https://adapterservice/api/source/connect`

The connect method configured the adapter inside the service. The request consists a bulk of configurations in JSON format or what else.

#### Request Body

| Name        | Type   | Description                                                                                  |
| ----------- | ------ | -------------------------------------------------------------------------------------------- |
| json object | string | <p><br><code>{</code><br>     <code>"configuration": "string"</code>  <br><code>}</code></p> |

{% tabs %}
{% tab title="200 No value in body if succeeded." %}

```javascript
{
  "messages": []
}
```

{% endtab %}
{% endtabs %}

Request example for a configuration of user and password:

```javascript
{
  "configuration": "{\"User\":\"test\",\"Password\":\"sd2rwe\"}"
}
```

## Source / Define

<mark style="color:green;">`POST`</mark> `https://adapterservice/api/source/define`

The define method returns the basic information of the adapter. Properties are name and description. Additionally a thumbnail can be added. The request for define method can also be empty.

#### Request Body

| Name        | Type   | Description         |
| ----------- | ------ | ------------------- |
| json object | string | Empty request body. |

{% tabs %}
{% tab title="200 A object with all adapter definitions returned." %}

```javascript
{
  "value": {
    "name": {
      "de-DE": "Basis Adapter",
      "en-EN": "Basic Adapter"
    },
    "description": {
      "de-DE": "Adapter zum testen der Schnittstelle.",
      "en-EN": "Adapter for testing the interface."
    },
    "thumbnail": {}
  },
  "messages": []
}
```

{% endtab %}
{% endtabs %}

## Source / Config

<mark style="color:blue;">`GET`</mark> `https://adapterservice/api/source/config`

Starts the configuration process. Returns a configuration step with initial parameters. The parameters represent a form input on the platform configuration.

#### Request Body

| Name        | Type   | Description         |
| ----------- | ------ | ------------------- |
| json object | string | Empty request body. |

{% tabs %}
{% tab title="200 " %}

```javascript
{
  "value": {
    "key": "string",
    "step": {
      "parameters": [
        {
          "key": "string",
          "type": 0,
          "name": {
            "additionalProp1": "string",
            "additionalProp2": "string",
            "additionalProp3": "string"
          },
          "description": {
            "additionalProp1": "string",
            "additionalProp2": "string",
            "additionalProp3": "string"
          },
          "error": {
            "additionalProp1": "string",
            "additionalProp2": "string",
            "additionalProp3": "string"
          },
          "value": {},
          "values": [
            {
              "key": "string",
              "name": {
                "additionalProp1": "string",
                "additionalProp2": "string",
                "additionalProp3": "string"
              }
            }
          ],
          "required": true
        }
      ]
    }
  }
}
```

{% endtab %}
{% endtabs %}

## Source / config

<mark style="color:green;">`POST`</mark> `https://adapterservice/api/source/config`

Handle the configuration process. In the request object *AdapterConfigHandleRequest* a list of configuration parameters are send to the service. This parameters are the input values from a configuration form.

#### Request Body

| Name        | Type   | Description                                                                                                                                                                                                                                                                                      |
| ----------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| json object | string | <p><code>{</code> <br>    <code>"key": "string",</code> <br>    <code>"parameters": {</code> <br>        <code>"additionalProp1": {},</code><br>        <code>"additionalProp2": {},</code><br>        <code>"additionalProp3": {}</code>         <br>     <code>}</code> <br><code>}</code></p> |

{% tabs %}
{% tab title="200 " %}

```javascript
{
  "messages": []
}
```

{% endtab %}
{% endtabs %}

In the adapter source you can now validate this parameter values. If the values are valid you returned a success step. Otherwise you send a configuration step back, similar the start conf method (further up). But at this time with error messages in failed parameter objects.

## Source / Capability

<mark style="color:blue;">`GET`</mark> `https://adapterservide/api/source/capability`

Get a list of capabilities which are supported by the source adapter (service).

#### Path Parameters

| Name        | Type   | Description         |
| ----------- | ------ | ------------------- |
| json object | string | Empty request body. |

{% tabs %}
{% tab title="200 In this example result the adapter supports four capabilities (action, schema, query and model)." %}

```javascript
{
  "value": {
    "capabilities": [
      0,
      6,
      5,
      3
    ]
  },
  "messages": []
}
```

{% endtab %}
{% endtabs %}

The available capability types ar listed under the [ISource topic](/extensibility-and-integration/source-adapter/source-adapter-interface.md#2-3-isource).

### **3.2 ActionController**

For the adapter call with the method *Execute*, an ActionController exist with the same method. This controller has only this one method as well.

```csharp
public async Task<ActionResult<SourceResult<object>>> Execute([FromBody] ActionExecuteRequest value)
{
    return await AdapterWrapper.Instance.Execute(value);
}
```

There are three properties in the *ActionExecuteRequest*.

| parameter name | type       | description                                                                      |
| -------------- | ---------- | -------------------------------------------------------------------------------- |
| ClassName      | str        | Name of the class where action executed (optional)                               |
| ActionName     | str        | Name of the action, it could be multiple actions in adapter                      |
| Parameters     | Dictionary | List of object, comparable with method parameters (key=param-name, value=object) |

## Action / Execute

<mark style="color:blue;">`GET`</mark> `https://adapterservice/api/action/execute`

#### Request Body

| Name        | Type   | Description                                                                                                                                                                                                                                                                                                                                                         |
| ----------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| json object | string | <p>example:<br><code>{</code> <br>   <code>"className": "myClass",</code> <br>   <code>"actionName": "myAction",</code> <br>   <code>"parameters": {</code> <br>      <code>"parameter1": "myValue",</code> <br>      <code>"parameter2": { 3, 5 },</code> <br>      <code>"parameter3": { "text 1", "text2" }</code> <br>    <code>}</code> <br><code>}</code></p> |

{% tabs %}
{% tab title="200 The response is an object type embedded in s SourceResult." %}

```javascript
{
    "value": {
        "object as serialized string",
    },
    "messages": []
}
```

{% endtab %}
{% endtabs %}

In this example response the object is a translation object, in property "value" (JSON):

```javascript
{
    "value": {
        "en-EN": "Result text. This is the request: 'XXy'.",
        "de-DE": "Ergebnis Text. Hier ist der Request: 'XXy'."
    },
    "messages": []
}
```

### **3.3 SchemaController**

The SchemaController has only one Method "Get" (HTTP POST). This method returns a list of schema definitions.

```csharp
public async Task<ActionResult<SourceResult<Schema>>> Get(SchemaGetRequest request)
{
    return await AdapterWrapper.Instance.Get(request);
}
```

The *SchemaGetRequest* object is currently empty. For more information about the response object see the [schema capability documentation](/extensibility-and-integration/source-adapter/source-adapter-interface.md#2-5-ischemacapability).

## Schema / Get

<mark style="color:green;">`POST`</mark> `https://adapterservice/api/schema/get`

Method for getting schema definition information.

#### Request Body

| Name        | Type   | Description         |
| ----------- | ------ | ------------------- |
| json object | string | empty request body. |

{% tabs %}
{% tab title="200 The response is a Schema object contains a list of classes and a list of links. The individual parameters explained in Documentation for the Adapter interfaces." %}

```javascript
{
  "value": {
    "classes": [
      {
        "name": "DummyClass",
        "fields": [
          {
            "name": "FieldStr",
            "length": 20,
            "defaultValue": null,
            "isNullable": false,
            "nameIntern": "intern-FieldStr",
            "dataSourceOption": null,
            "isReadOnly": false,
            "dataType": 2
          },
          {
            "name": "FieldDate",
            "length": 20,
            "defaultValue": null,
            "isNullable": false,
            "nameIntern": "intern-FieldDate",
            "dataSourceOption": null,
            "isReadOnly": false,
            "dataType": 3
          },
          {
            "name": "FieldInt",
            "length": 20,
            "defaultValue": null,
            "isNullable": false,
            "nameIntern": "intern-FieldInt",
            "dataSourceOption": null,
            "isReadOnly": false,
            "dataType": 4
          },
          {
            "name": "Created",
            "length": 20,
            "defaultValue": null,
            "isNullable": false,
            "nameIntern": "intern-Created",
            "dataSourceOption": null,
            "isReadOnly": false,
            "dataType": 3
          }
        ]
      }
    ],
    "links": [
      {
        "name": "#lnk-002c",
        "primaryClassName": "ClVehicle",
        "primaryFieldName": "TestField-ClVehicle-8",
        "foreignClassName": "ClMotor",
        "foreignFieldName": "TestField-ClMotor-2"
      }
    ]
  },
  "messages": []
}
```

{% endtab %}
{% endtabs %}

### **3.4 QueryController**

The QueryController has only one method and passes the request through to the adapter.

```csharp
public async Task<ActionResult<SourceResult<ModelList>>> Execute(QueryExecuteRequest request)
{
    return await AdapterWrapper.Instance.Execute(request);
}
```

The *QueryExecuteRequest* object needs a Query with the class ("From"), the columns and a list of conditions. Optional we can add Joins and Paginations. You find a detailed description of the individual properties in the [SourceAdapter Documentation](/extensibility-and-integration/source-adapter/source-adapter-interface.md).

## Query / Execute

<mark style="color:green;">`POST`</mark> `https://adapterservice/apiquery/execute`

#### Request Body

| Name        | Type   | Description                                 |
| ----------- | ------ | ------------------------------------------- |
| json object | string | serialized query object. See example below. |

{% tabs %}
{% tab title="200 The result is a model list. The Model object contains a Key-Value Pair List presents the properties of the model and Addional the number of entries in the complete list ("total")." %}

```javascript
{
  "value": {
    "models": [
      {
        "additionalProp1": { "property value" },
        "additionalProp2": {},
        "additionalProp3": {}
      }
    ],
    "total": 1
  }
}
```

{% endtab %}
{% endtabs %}

> ! **Important**: specify the *expression* type in json request.

Example request (json): \
For more information about the Query object see the [SourceAdapter Documentation](/extensibility-and-integration/source-adapter/source-adapter-interface.md#2-6-iquerycapability).

```javascript
{
    "Query": {
        "From": {
            "ClassName": "TestClass",
            "Alias": "c"
        },
        "Joins": null,
        "Columns": [
            {
                "Expression": {
                    "$type": "Tivity.Plugins.Models.ExpressionField, Tivity.Plugins",
                    "ClassAlias": null,
                    "FieldName": "Field1"
                },
                "Alias": null
            },
            {
                "Expression": {
                    "$type": "Tivity.Plugins.Models.ExpressionField, Tivity.Plugins",
                    "ClassAlias": null,
                    "FieldName": "Field3"
                },
                "Alias": null
            }
        ],
        "Conditions": [
            {
                "LeftParentheses": 1,
                "LeftExpression": {
                    "$type": "Tivity.Plugins.Models.ExpressionQuery, Tivity.Plugins",
                    "From": {
                        "ClassName": "TestClass",
                        "Alias": "c"
                    },
                    "Joins": null,
                    "Column": {
                        "Expression": {
                            "$type": "Tivity.Plugins.Models.ExpressionField, Tivity.Plugins",
                            "ClassAlias": null,
                            "FieldName": "Field1"
                        },
                        "Alias": null
                    },
                    "Conditions": null
                },
                "IsNegated": false,
                "Operator": 5,
                "RightExpression": {
                    "$type": "Tivity.Plugins.Models.ExpressionValue, Tivity.Plugins",
                    "Value": 3
                },
                "RightParentheses": 1,
                "LogicalOperator": 1
            },
            {
                "LeftParentheses": 1,
                "LeftExpression": {
                    "$type": "Tivity.Plugins.Models.ExpressionQuery, Tivity.Plugins",
                    "From": {
                        "ClassName": "TestClass",
                        "Alias": "c"
                    },
                    "Joins": null,
                    "Column": {
                        "Expression": {
                            "$type": "Tivity.Plugins.Models.ExpressionField, Tivity.Plugins",
                            "ClassAlias": null,
                            "FieldName": "Field2"
                        },
                        "Alias": null
                    },
                    "Conditions": null
                },
                "IsNegated": true,
                "Operator": 0,
                "RightExpression": {
                    "$type": "Tivity.Plugins.Models.ExpressionValue, Tivity.Plugins",
                    "Value": "mySearchTxt"
                },
                "RightParentheses": 1,
                "LogicalOperator": 0
            }
        ],
        "Pagination": null
    },
    "Credentials": null,
    "TransactionKey": null
}
```

### **3.5 ModelController**

For manipulate an instance (or model) the ModelController is responsible. This controller has three methods: create, update and delete.

```csharp
public async Task<ActionResult<SourceResult<Model>>> Create(ModelCreateRequest request)
{
    return await AdapterWrapper.Instance.Create(request);
}
public async Task<ActionResult<SourceResult>> Update(ModelUpdateRequest request)
{
    return await AdapterWrapper.Instance.Update(request);
}
public async Task<ActionResult<SourceResult>> Delete(ModelDeleteRequest request)
{
    return await AdapterWrapper.Instance.Delete(request);
}
```

For more information about the model methods create, update and delete see the [SourceAdapter Documentation about ModelCapability](/extensibility-and-integration/source-adapter/source-adapter-interface.md#2-7-imodelcapability).

## Model / Create

<mark style="color:green;">`POST`</mark> `https://adapterservice/api/model/create`

To create a new instance of a class, the create method can be used.

#### Request Body

| Name        | Type   | Description                                                                                                                                                                                                                                                                                                                                         |
| ----------- | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| json object | string | <p>The request object needs a class name of an existing class and the new instance as a list of key-value pairs.<br><code>{</code><br>   <code>"className": "myClass",</code><br>   <code>"model": {</code><br>      <code>"property1": "value1",</code>    <br>      <code>"property2": "value2"</code><br>   <code>}</code><br><code>}</code></p> |

{% tabs %}
{% tab title="200 The result is the model that was newly created. If a field in the new model is autogenerated, it appears in result object (in this axample the 'created' field)." %}

```javascript
{
  "value": {
    "FieldStr": "value1",
    "FieldInt": 5,
    "FieldDate": "2019-07-29T17:16:42.9744742+02:00",
    "Created": "2019-07-29T18:05:22.0143352+02:00"
  },
  "messages": []
}
```

{% endtab %}
{% endtabs %}

## Model / Update

<mark style="color:orange;">`PUT`</mark> `https://adapterservice/api/model/update`

To manipulate an existing instance usually the update method is used. The ModelUpdateRequest needs the class name and a key for identify the model and the instance itself with all changed properties.

#### Request Body

| Name        | Type   | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| ----------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| json object | string | <p>- className: underlying class of the instance<br>- key: instance identifier<br>- model: properties which are updated<br><br><code>{</code><br>   <code>"className": "myClass",</code><br>    <code>"key": {</code><br>        <code>"propertyId": "identifier"</code>   <br>    <code>},</code><br>    <code>"model": {</code><br>        <code>"property1": "newValue1",</code><br>        <code>"property2": "newValue2"</code><br>    <code>}</code><br><code>}</code></p> |

{% tabs %}
{% tab title="200 " %}

```javascript
{
  "messages": []
}
```

{% endtab %}
{% endtabs %}

## Model / Delete

<mark style="color:red;">`DELETE`</mark> `https://adapterservice/api/model/delete`

For delete an instance (model) call the delete method. The request needs the class name and a key for identify the instance.

#### Path Parameters

| Name        | Type   | Description                                                                                                                                                                                                                                                                  |
| ----------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| json object | string | <p>- className: underlying class of the instance<br>- key: instance identifier<br><code>{</code><br>    <code>"className": "myClass",</code><br>    <code>"key": {</code><br>        <code>"propertyId": "identifier"</code>    <br>    <code>}</code><br><code>}</code></p> |

{% tabs %}
{% tab title="200 If the instance deleted successfully, the message is empty." %}

```javascript
{
  "messages": []
}
```

{% endtab %}
{% endtabs %}

### **3.6 DocumentController**

In order to obtain a document you have to use the *Download* method. As a result, only the content of a document is returned. This is a (byte) stream with the content type information in response header.

```csharp
public async Task<IActionResult> Download(DocumentDownloadRequest request)
```

For better understanding of the method and the request object see chapter [IDocumentCapability in SourceAdapter](/extensibility-and-integration/source-adapter/source-adapter-interface.md#2-8-idocumentcapability).

## Document / Download

<mark style="color:green;">`POST`</mark> `https://adapterservice/api/document/download`

For download a content you need to send the information about the class and a document identifier. These values are set in the *DocumentDowloadRequest*. The class name is useful to assign the document content to a certain class. The key value is required to uniquely identify a document. This identifier can be a unique name, a path or a GUID as well.

#### Request Body

| Name        | Type   | Description                                                                                                                                                                                                                   |
| ----------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| json object | string | <p>- className: name of the assigned class (optional)<br>- key: unique identifier (requred)<br><code>{</code><br>   <code>"className": "NameOfClass",</code>    <br>   <code>"key": "identifier"</code><br><code>}</code></p> |

{% tabs %}
{% tab title="200 If the call is successful the service return a FileStreamResult which represents an ActionResult that writes a file from a stream to the response and the correct content type is added to the header. If an error occurs, a SourceResult with error messages returned." %}

```
```

{% endtab %}
{% endtabs %}

Example request (json):

```javascript
{
  "className": "DummyDocumentClass",
  "key": "75a1bec0-c885-41c7-a93b-59e11441eac5"
}
```

### **3.7 FileUpload-StorageController**

When files need to be uploaded, the StorageController is also required. This controller ensures that the file stream and an identity key (FileId) are received on server site. The REST service provides for storage the file contents (streams) e.g. save on disk, in database or on memory cache.

```csharp
public async Task<ActionResult<SourceResult>> Upload([FromForm]StoragePutRequest request)
```

## Storage / Upload

<mark style="color:green;">`POST`</mark> `https://adapterservice/api/storage/upload`

The upload request is a multipart/form-data content-type which consists of the file stream and the formdata FileId.

#### Request Body

| Name          | Type   | Description                   |
| ------------- | ------ | ----------------------------- |
| FileId        | string | unique identifier of the file |
| (file) stream | object | the file stream               |

{% tabs %}
{% tab title="200 If upload is succeeded, the message is empty." %}

```javascript
{
  "messages": []
}
```

{% endtab %}
{% endtabs %}

During the upload process the platform additionally trigger an execute call (see 3.2 [ActionController](/extensibility-and-integration/source-adapter/rest-service.md#action-execute)). There is an entry in the 'parameters' dictionary with the file metadata or a list of files. For Details see the [SourceAdapter documentation](/extensibility-and-integration/source-adapter/source-adapter-interface.md#2-4-iactioncapability).

Example execute request for a list of files (json):

```javascript
{
    "parameters": {
        "parameter1": [
            {
                "FileId": "40765ec8-492f-47ed-9797-f9ff5dc35041",
                "MimeType": "text/plain",
                "FileName": "example01.txt"
            },
            {
                "FileId": "21a3b5da-fd63-4b4c-be43-87a8a07121e2",
                "MimeType": "text/plain",
                "FileName": "example02.txt"
            }
        ]
    }
}
```

Upload asynchronous calls as sequence diagram:

<div align="left"><img src="/files/-M9--HOm2QAp_mm5AWv1" alt=""></div>

It is essential for success uploading files that the upload method exist in the REST service and the adapter capability *IActionCapability* with the execute method was implemented as well. This execute method must be able to process the file parameters accordingly (see example request above).


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.tivity.one/extensibility-and-integration/source-adapter/rest-service.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
