# Custom form experience

A breakdown of the API calls involved with the Custom form experience

## Register a user

![register-user-in-your-app](https://tray.ai/documentation/images/platform/embedded/delivery-methods/custom-experience/494c0e1c-6b42885f_register-user-in-your-app.png)

### User journey map

![createExternalUser : user journey diagram](https://tray.ai/documentation/images/platform/embedded/delivery-methods/custom-experience/5KPMrHvahx2baWgg8cgakO_integrationwalkthrough1.drawio%20%283%29.png)

### createExternalUser API mutation

The first thing to do in your application is to register a user with [createExternalUser (master token)](https://tray.ai/documentation/developer/embedded-apis/users#graphql-create-user):

```graphql
 mutation {
  createExternalUser(input: \{name: "Billy Bluehat", externalUserId: "96xxxxxxd7"\}) {
   userId
  }
 }
```

This will return the **userId** in the result:

```json
{
 "data": {
  "createExternalUser": {
   "userId": "fbb96559-xxxx-xxxx-xxxx-5552c2d2fca4"
  }
 }
}
```

### createExternalUser API mutation sample code

The accordion below shows the function and the API code for this call.

### useJWT router function

This is the entry point to the application. In the marketplace app, the [utils/hooks.js](https://github.com/trayio/nextjs-marketplace-app/blob/e6a1882c2a6b79473bb463ca76be7feb7a0d9e83/utils/hooks.js#L57) file shows this function in use:

```javascript
export function useJWT() {
    // console.log('useUser', token, user)
    const router = useRouter()
    const { jwt } = router.query
    if (!jwt && router.isReady) \{
        // If there is now json web token present, throw an error
        throw Error('Invalid User Session!')
      \}    
    const \{ data, error, isValidating \} = useSWRImmutable(jwt ? /api/jwt?jwt=${encodeURIComponent(jwt)} : null, fetcher, { shouldRetryOnError: false })
    if (error) \{
        throw error;
    \}    
    return {
        jwt,
        user: \{
            user: get(data, 'user'),
            userToken: get(data, 'userToken'),
        \},
        claims: get(data, 'claims'),
        isLoading: !error && !data,
        isValidating
    }
}
```

From here the control is passed to JWT handler function in the next step.

### JWT handler function code

In the marketplace app, the [pages/api/jwt.js](https://github.com/trayio/nextjs-marketplace-app/blob/e6a1882c2a6b79473bb463ca76be7feb7a0d9e83/pages/api/jwt.js#L6) file shows this function code in use:

```javascript
export default async function handler(req, res) {
    try {
        const { jwt: queryJWT } = req.query;
        const masterToken = await getMasterToken(req.query);
        let \{ user, claims \} = await validateJsonWebToken(masterToken, queryJWT);
        if (claims.expires) {
            const expires = new Date(claims.expires);
            const now = new Date();
            if (now.getTime() > expires.getTime()) {
                throw Error('Token has expired')
            }
        }
        if (!user) \{
            console.log('Could not find user with id', claims.id, 'creating one')
            await mutations.createUser(claims.id, claims.name, masterToken)
            user = await queries.getUser(claims.id, masterToken);
        \} else {
            // TODO: update users name from claim
        }
        const userToken = await mutations.createUserToken(user.id, masterToken);
        res.status(200).json(\{ user, userToken, claims \})
    } catch (e) {
        console.error(e);
        res.status(400).json({ message: e.message })
    }
}
```

### createExternalUser API mutation code

In the marketplace app, the [utils/graphql.js](https://github.com/trayio/nextjs-marketplace-app/blob/e6a1882c2a6b79473bb463ca76be7feb7a0d9e83/utils/graphql.js#L193) file shows this API mutation in use:

```javascript
createUser: async (externalUserId, name, token = process.env.MASTER_TOKEN) => {
        return request(token, gql
            mutation createExternalUser($externalUserId: String!, $name: String!) {
                createExternalUser(input:\{ externalUserId: $externalUserId, name: $name \}) {
                    userId
                }
            }
        , \{
            externalUserId,
            name
        \}).then(data => get(data, 'createExternalUser.userId'))
    },
```

### Authorize API mutation

The **userId** can then be used to create an **accessToken** with [authorize (master token)](https://tray.ai/documentation/developer/embedded-apis/users#graphql-create-user-token):

```graphql
mutation {
 authorize(input: {userId: "fbb96559-xxxx-xxxx-xxxx-5552c2d2fca4"}) {
  accessToken
 }
}
```

Result:

```json
{
 "data": {
  "authorize": {
   "accessToken": "0c8c8e16e39e4xxxxxxxxxxxxxxxxxxxxxx49545b584e307063710d1ee"
  }
 }
}
```

### Authorize API mutation sample code

The accordion below shows the function and the API code for this call.

### JWT handler function code

In the marketplace app the [pages/api/jwt.js](https://github.com/trayio/nextjs-marketplace-app/blob/e6a1882c2a6b79473bb463ca76be7feb7a0d9e83/pages/api/jwt.js#L6) file shows that once a user is created we make the function call to generate a userToken:

```javascript
export default async function handler(req, res) {
    try {
        const { jwt: queryJWT } = req.query;
        const masterToken = await getMasterToken(req.query);
        let \{ user, claims \} = await validateJsonWebToken(masterToken, queryJWT);
        if (claims.expires) {
            const expires = new Date(claims.expires);
            const now = new Date();
            if (now.getTime() > expires.getTime()) {
                throw Error('Token has expired')
            }
        }
        if (!user) \{
            console.log('Could not find user with id', claims.id, 'creating one')
            await mutations.createUser(claims.id, claims.name, masterToken)
            user = await queries.getUser(claims.id, masterToken);
        \} else {
            // TODO: update users name from claim
        }
        const userToken = await mutations.createUserToken(user.id, masterToken);
        res.status(200).json(\{ user, userToken, claims \})
    } catch (e) {
        console.error(e);
        res.status(400).json({ message: e.message })
    }
}
```

### authorize API mutation code

In the demo app the [utils/graphql.js](https://github.com/trayio/nextjs-marketplace-app/blob/e6a1882c2a6b79473bb463ca76be7feb7a0d9e83/utils/graphql.js#L206) file shows the graphql mutation in use:

```javascript
createUserToken: async (userId, token = process.env.MASTER_TOKEN) => {
        return request(token, gql
            mutation authorize($userId: ID!) {
                authorize(input:{ userId: $userId }) {
                    accessToken
                }
            }
        , {
            userId
        }).then(data => get(data, 'authorize.accessToken'))
    },
```

## Log existing user in

![api calls step by step : login-user : image](https://tray.ai/documentation/images/platform/embedded/delivery-methods/custom-experience/45bUHgUOH9sQIvRLRsjoKr_login-user.png)

### User journey map

![api calls step by step : loginExistingUser : user journey map](https://tray.ai/documentation/images/platform/embedded/delivery-methods/custom-experience/5O6CEc3tQyISO9Jhxmx4Zy_integrationwalkthrough1.drawio%20%284%29.png)

### Retrieve existing user sample code

The accordion below shows the function and the API code for this call.

### JWT handler function code

The entry point for login is through the useJWT router function as shown in registering the user, Post that JWT handler function picks up control.
In the marketplace app, the [pages/api/jwt.js](https://github.com/trayio/nextjs-marketplace-app/blob/e6a1882c2a6b79473bb463ca76be7feb7a0d9e83/pages/api/jwt.js#L13) file shows this function code in use:

```javascript
export default async function handler(req, res) {
    try {
        const { jwt: queryJWT } = req.query;
        const masterToken = await getMasterToken(req.query);
        let \{ user, claims \} = await validateJsonWebToken(masterToken, queryJWT);
        if (claims.expires) {
            const expires = new Date(claims.expires);
            const now = new Date();
            if (now.getTime() > expires.getTime()) {
                throw Error('Token has expired')
            }
        }
        if (!user) \{
            console.log('Could not find user with id', claims.id, 'creating one')
            await mutations.createUser(claims.id, claims.name, masterToken)
            user = await queries.getUser(claims.id, masterToken);
        \} else {
            // TODO: update users name from claim
        }
        const userToken = await mutations.createUserToken(user.id, masterToken);
        res.status(200).json(\{ user, userToken, claims \})
    } catch (e) {
        console.error(e);
        res.status(400).json({ message: e.message })
    }
}
```

In the above code, the function call to `validateJsonWebToken` attempts to find the user.

### validateJsonWebToken function code

In the marketplace app the [utils/security.js](https://github.com/trayio/nextjs-marketplace-app/blob/e6a1882c2a6b79473bb463ca76be7feb7a0d9e83/utils/security.js#L26) file shows this function code in use:

```javascript
export async function validateJsonWebToken(masterToken, jwtToDecode) {
    if (!masterToken) {
        throw Error('Expecting a valid token query parameter')
    }  
    console.log('got master token for request', masterToken)
    if (!jwtToDecode) {
        throw Error('Expecting a valid jwt query parameter')
    }        
    const claims = jwt.verify(jwtToDecode, masterToken)
    if (!claims.id) {
        throw Error('Expecting a valid user.id JWT claim')
    }
    if (!claims.name) {
        throw Error('Expecting a valid user.name JWT claim')
    }    
    console.log('getting user for id', claims.id)
    let user = await queries.getUser(claims.id, masterToken);
    console.log('queried user', user)
    return \{
        user,
        claims
    \};
}
```

In the code above, the `verify` function decodes the JWT token and extracts the `id`, `name`, `expires`, `tagFilters` value from it. We then use the `id` to query the user using getUser function.

### getUser API query sample code

In the marketplace app, the [utils/graphql.js](https://github.com/trayio/nextjs-marketplace-app/blob/e6a1882c2a6b79473bb463ca76be7feb7a0d9e83/utils/graphql.js#L171) file shows this function code in use:

```javascript
getUser: async (externalUserId, token = process.env.MASTER_TOKEN) => {
        return request(token, gql
        query getUser($externalUserId: String!) {
            users(criteria:{ externalUserId: $externalUserId}) {
            edges {
              node {
                id
                name
                externalUserId
              }
            }
          }
        }
        , {
            externalUserId
        }).then(data => get(data, 'users.edges[0].node'))
    }
```

> **Info:** The best practice here would be to pull the end user information from your own database. You can do it by using the id (email, username etc.) that end users use to log in to your marketplace as their external ID and create a Tray user. Store this External ID that you used in the [createExternalUser](https://tray.ai/documentation/developer/embedded-apis/users#graphql-create-user) API call and the returned user ID in your database.
> This way, you wouldn't have to make this extra graphql query call. We have not followed the best practice in the marketplace app to show how you can achieve this with the API call.

## List available integrations

![list-available-solutions](https://tray.ai/documentation/images/platform/embedded/delivery-methods/custom-experience/494c0e1c-fed61651_listSolutions.png)

### User journey map

![api calls step by step : listSolutions : user journey map](https://tray.ai/documentation/images/platform/embedded/delivery-methods/custom-experience/6UACqf9IzRAsmxTcgIuc5P_integrationwalkthrough1.drawio%20%285%29.png)

### solutions API query

The Solutions available to your End Users can be listed using the [solutions query (master token)](https://tray.ai/documentation/developer/embedded-apis/solutions#graphql-get-solutions):

```graphql
query {
  viewer {
   solutions {
    edges {
     node {
      id
      title
      description
      tags
      customFields {
       key
       value
      }
     }
     cursor
    }
    pageInfo {
      hasPreviousPage
      hasNextPage
      startCursor
      endCursor
    }
   }
  }
 }
```

Within the **node** of the result you will find the Solution **id**:

```json
{
 "data": {
  "viewer": {
   "solutions": {
    "edges": [
     {
      "node": {
       "id": "91c0f56f-xxxx-xxxx-xxxx-184686978ea5",
       "title": "Webhook demo",
       "description": "",
       "tags": [],
       "customFields": [],
       "configSlots": [
        \{
         "externalId": "external_slack-channel",
         "title": "slack-channel",
         "defaultValue": "\"CxxxxTP\""
        \},
        \{
         "externalId": "external_slack-message",
         "title": "slack-message",
         "defaultValue": "\"The webhook for your solution instance has been triggered\""
        \}
       ]
      },
      "cursor": "OTFjMGY1NmYtMGE1MC00M2Y4LWExMTAtMTg0Njg2OTc4ZWE1"
     }
    ],
    "pageInfo": \{
     "hasNextPage": false,
     "endCursor": "OTFjMGY1NmYtMGE1MC00M2Y4LWExMTAtMTg0Njg2OTc4ZWE1",
     "hasPreviousPage": false,
     "startCursor": "OTFjMGY1NmYtMGE1MC00M2Y4LWExMTAtMTg0Njg2OTc4ZWE1"
    \}
   }
  }
 }
}
```

Note that the above pageInfo values can be used for [pagination](https://developer.tray.io/developer/getting-started/implementation-notes/pagination)

### solutions API query sample code

The accordion below shows the function and the API code for this call.

### Solutions handler function code

In the marketplace app, the [pages/api/solutions.js](https://github.com/trayio/nextjs-marketplace-app/blob/e6a1882c2a6b79473bb463ca76be7feb7a0d9e83/pages/api/solutions.js#L5) file shows this function code in use:

```javascript
export default async function handler(req, res) {
  try {
    const \{ token: userToken, tags \} = req.query;
    if (!userToken) {
      throw Error('Expecting a valid token parameter')
    }
    const tagsToFilter = tags ? tags.split(',') : [];
    const solutions = await queries.solutions(userToken, tagsToFilter);
    res.status(200).json(\{ solutions: map(solutions, e => e.node) \})
  } catch (e) {
    console.error(e);
    res.status(400).json({ message: e.message })
  }
}
```

### Solutions query code

In the marketplace app, the [utils/graphql.js](https://github.com/trayio/nextjs-marketplace-app/blob/e6a1882c2a6b79473bb463ca76be7feb7a0d9e83/utils/graphql.js#L17) file shows this API query in use:

```javascript
solutions: async (token = process.env.MASTER_TOKEN, tags = []) => {
        return request(token, gql
        query solutions($tags: [String!]) {
            viewer {
              solutions(criteria: { tags: $tags}) {
                edges {
                  node {
                    id
                    title
                    description
                    tags
                    customFields {
                      key
                      value
                    }
                  }
                  cursor
                }
                pageInfo {
                  hasNextPage
                  hasPreviousPage
                  startCursor
                  endCursor
                }
              }
            }
          }          
        , {
          tags
        }).then(data => get(data, 'viewer.solutions.edges'))
    },
```

## User configures integration

![customFormFigma](https://tray.ai/documentation/images/platform/embedded/delivery-methods/custom-experience/gxifF3SQ7SOFDUwRC9W2d_customFormFigma.png)
The auth only dialog experience is repeated for each service auth that the end user provides as described above. Post that, you need to update solution instance to attach config data.
Follow these steps to present this experience for end users:

### Create solution instance

### User journey map

![api calls step by step : createSolutionInstance : user journey map](https://tray.ai/documentation/images/platform/embedded/delivery-methods/custom-experience/oofdHt0VCnjznt8aom7ig_createSolutionInstance.png)

### createSolutionInstance API mutation

With the correct **solutionId** from above use [createSolutionInstance (user token)](https://tray.ai/documentation/developer/embedded-apis/solution-instances#graphql-create-solution-instance) to create a Solution Instance:

```graphql
mutation {
 createSolutionInstance(input: \{
   solutionId: "e2ed06d9-xxxx-xxxx-xxxx-218ae63a30b6",
   instanceName: "Webhook demo"
 \}) {
  solutionInstance {
   id
   name
   enabled
   created
  }
 }
}
```

The result will return the id of the Solution Instance:

```json
{
 "data": {
  "createSolutionInstance": {
   "solutionInstance": {
    "id": "9e2e9b86-xxxx-xxxx-xxxx-72e84e266d4d",
    "name": "Webhook demo",
    "enabled": false,
    "created": "2020-10-02T13:06:41.605Z",
    "solution": \{
     "id": "91c0f56f-xxxx-xxxx-xxxx-184686978ea5",
     "title": "Webhook demo",
     "description": ""
    \},
    "authValues": [],
    "configValues": [],
    "workflows": {
     "edges": [
      {
       "node": \{
        "id": "e79e7a66-xxxx-xxxx-xxxx-16b67f870718",
        "sourceWorkflowId": "804c5f50-9e7d-4b50-858b-9338c507ff45",
        "sourceWorkflowName": "Webhook demo",
        "triggerUrl": "https://0ed9f5b2-xxxx-xxxx-xxxx-44ab9507e4ae.trayapp.io"
       \}
      }
     ]
    },
    "solutionVersionFlags": \{
     "hasNewerVersion": false,
     "requiresUserInputToUpdateVersion": false,
     "requiresSystemInputToUpdateVersion": false
    \}
   }
  }
 }
}
```

### createSolutionInstance API mutation sample code

The accordion below shows the function and the API code for this call.

### connectSolution function code breakdown

In the marketplace app, the [utils/tray.js](https://github.com/trayio/nextjs-marketplace-app/blob/e6a1882c2a6b79473bb463ca76be7feb7a0d9e83/utils/tray.js#L17) file shows the `connectSolution` function. Here's a breakdown of this function:
![connectSolution code breakdown](https://tray.ai/documentation/images/platform/embedded/delivery-methods/custom-experience/1nW34JN2QBhUzCi4QPAcI2_connectSolution%20code%20breakdown.png)

### connectSolution function code

In the marketplace app, the [utils/tray.js](https://github.com/trayio/nextjs-marketplace-app/blob/e6a1882c2a6b79473bb463ca76be7feb7a0d9e83/utils/tray.js#L17) file shows the `connectSolution` function in use:

```javascript
export const connectSolution = async (user, jwt, solution, config, reload) => {
    return new Promise(async (resolve, reject) => {
        const disableAutoEnable = ['yes', 'true'].indexOf(get(solution, 'customFieldsMap.disableautoenable')) != -1;
        const newInstance = await axios.post(/api/create-instance, {
            userToken: user.userToken,
            solutionId: solution.id,
            instanceName: My instnace of ${solution.id}
        }).then(r => r.data).catch(e => { throw Error(Error creating solution instance: $\{get(e, 'response.data.message')\}) })

        // Create an auth code
        const { code: authorizationCode } = await axios.get(/api/code, { params: { jwt } }).then(r => r.data).catch(e => { throw Error(Error getting authorization code: $\{get(e, 'response.data.message')\}) })

        openConfigWizard(
            https://${config.get('settings.wizardDomain')}/external/solutions/${config.get('settings.partnerId')}/configure/${newInstance.id}?code=${authorizationCode},
            async () => {
                console.log('wizard success')
                // on started, set loading etc
                if (!disableAutoEnable) {
                    // If disable auto enable is false, 
                    // we want to make an enable instance call
                    await toggleInstance(user, { id: newInstance.id }, true)
                }
                reload && reload(newInstance);
                resolve();
            },
            async () => {
                console.log('wizard error')
                // on error, delete
                await axios.post(/api/delete-instance, \{
                    userToken: user.userToken,
                    solutionInstanceId: newInstance.id,
                \}).then(r => r.data).catch(e => { throw Error(Error cleaning up solution instance: $\{get(e, 'response.data.message')\}) })
                console.log('deleted instance')
                reload && reload();
                reject(Error(config.get('content.connectCloseErrorMessage')));
            },
            config.get('settings.windowMode')
        );
    })
}
```

### createSolutionInstance handler code

In the marketplace app, the [pages/api/create-instance.js](https://github.com/trayio/nextjs-marketplace-app/blob/e6a1882c2a6b79473bb463ca76be7feb7a0d9e83/pages/api/create-instance.js#L3) file shows this function code in use:

```javascript
export default async function handler(req, res) {
  try {
    const \{ userToken, solutionId, instanceName, authValues, configValues \} = req.body;
    const instance = await mutations.createSolutionInstance(userToken, solutionId, instanceName, authValues, configValues);
    res.status(200).json(instance)
  } catch (e) {
    console.error(e);
    res.status(400).json({ message: e.message })
  }
}
```

### &#x20;createSolutionInstance mutation sample code

In the marketplace app, the [utils/graphql.js](https://github.com/trayio/nextjs-marketplace-app/blob/e6a1882c2a6b79473bb463ca76be7feb7a0d9e83/utils/graphql.js#L230) file shows this API mutation in use:

```javascript
createSolutionInstance: async (userToken, solutionId, name, authValues = [], configValues = []) => {
      return request(userToken, gql
            mutation create($solutionId: ID!, $name: String!, $authValues: [AuthValue!]!, $configValues: [ConfigValue!]!) {
              createSolutionInstance(input: \{solutionId: $solutionId, instanceName: $name, authValues: $authValues, configValues: $configValues\}) {
                solutionInstance {
                  id
                  name
                  enabled
                  solution {
                    id
                    title
                    description
                    tags
                    customFields {
                      key
                      value
                    }
                  }
                  solutionVersionFlags {
                    hasNewerVersion
                    requiresUserInputToUpdateVersion
                  }
                }
              }
            }
        , \{
          solutionId,
          name,
          authValues,
          configValues,
      \}).then(data => get(data, 'createSolutionInstance.solutionInstance'))
    }
```

### Initialize Custom form

> **Info:** Since the Integration Marketplace app has been built for the config wizard experience, we will not be showing code samples for this step as it requires initializing your user experience. However, we will show the API calls you need to make and leave the code up to you.

### User journey map

![customForm configures userJourney](https://tray.ai/documentation/images/platform/embedded/delivery-methods/custom-experience/2RM656bGwZUgytCsK3RESw_customForm_userJourney.png)

### 1 - Launch custom form

This form is launched as the end user clicks on configure button for the solution which would create the solution instance and present this custom form to the user. The form could have sections for adding auths and configs as shown here:
![pathways cutomForm experience](https://tray.ai/documentation/images/platform/embedded/delivery-methods/custom-experience/32CkdbwAgVwdnouGTDogoZ_customForm_ui.png)

### 2 - End user adds a service auth

This should launch the auth only dialog for this service where the end user can provide auth. You need to repeat the following steps for every service auth that end user adds:

### User journey map

![authOnlyDialog configures userJourney](https://tray.ai/documentation/images/platform/embedded/delivery-methods/custom-experience/1I0morbomczMqtDMlVExmm_authOnlyDialog_userJourney.png)

### 1 - Create authorization code

You will need to use [generateAuthorizationCode (using master token)](https://tray.ai/documentation/developer/embedded-apis/users#graphql-create-config-wizard-auth-code) to get the one-time auth-only code for the End User.

### generateAuthorizationCode API mutation

```graphql
mutation {
 generateAuthorizationCode(input: {userId: "fbb96559-xxxx-xxxx-xxxx-5552c2d2fca4"}) {
  authorizationCode
  clientMutationId
 }
}
```

This will return **a one-time use code** which can be used in the **Auth dialog url**:

```json
{
 "data": {
  "generateAuthorizationCode": \{
   "authorizationCode": "b8aab26cxxxxxxxxxxxxxxxxxxxx776c7bba7977",
   "clientMutationId": null
  \}
 }
}
```

### 2 - Compose the Auth dialog URL

Now you can compose the Auth dialog popup URL using the returned **solutionInstanceId** (createSolutionInstance mutation) and \*\*authorizationCode \*\*(generateAuthorizationCode mutation) from the calls above:
`https://embedded.tray.io/external/auth/create/{embeddedId}/{solutionInstanceId}/{externalAuthId}?code={authorizationCode}`
`externalAuthId` is the External ID of the auth in your solution.
![set-external-id](https://tray.ai/documentation/images/platform/embedded/delivery-methods/custom-experience/fd0bc8dd-2e5f4f24_authOnlyDialog.png)
Remember that [embeddedId](https://tray.ai/documentation/platform/embedded/overview/preparing-your-environment/#embedded-id) is the value that you set when configuring your [Config Wizard CSS](https://tray.ai/documentation/platform/embedded/advanced-features/whitelabelling/config-wizard).
Note that your exact URL may differ if you are in a non-US region. Please see the [note above](#notes-on-auth-dialog-url-and-region) for more details.

### Open the URL in browser

![authOnlyDialog ss](https://tray.ai/documentation/images/platform/embedded/delivery-methods/custom-experience/3wxG4ksavHz3HV2zsN8Avl_authOnlyDialog_ss.png)

### Fill the auth data

Enter the auth data for the End User.

### 3 - Update Solution Instance with Authorization

Final step is to use the [Update Solution Instance](https://tray.ai/documentation/developer/embedded-apis/solution-instances#graphql-update-solution-instance) mutation to update the **solutionInstanceId** with authValues for the End User (this requires an access token for your End User as per [Create User Token](https://tray.ai/documentation/developer/embedded-apis/users#graphql-create-user-token))

### updateSolutionInstance API mutation

Use the [updateSolutionInstance](https://tray.ai/documentation/developer/embedded-apis/solution-instances#graphql-update-solution-instance) mutation to set `enabled` to 'true'

```js
mutation {
  updateSolutionInstance(
    input: {
      solutionInstanceId: "9e2e9b86-xxxx-xxxx-xxxx-72e84e266d4d"
      instanceName: "Webhook demo"
      authValues: [
        {
          externalId: "external_slack_authenticaion"
          value: "45ec3104-xxxx-xxxx-xxxx-25e810c0c0e8"
        }
      ]
      enabled: true
    }
  ) {
    solutionInstance {
      id
      name
      enabled
      created
      authValues {
        externalId
        authId
      }
    }
  }
}
```

The returned data will confirm that the Solution Instance contains the authValues:

```json
{
 "data": {
  "updateSolutionInstance": {
   "solutionInstance": {
    "id": "9e2e9b86-xxxx-xxxx-xxxx-72e84e266d4d",
    "name": "Webhook demo",
    "enabled": true,
    "created": "2020-10-02T13:06:41.605Z",
    "authValues": [
     \{
      "externalId": "external_slack_authentication",
      "authId": "45ec3104-xxxx-xxxx-xxxx-25e810c0c0e8"
     \}
    ]
   }
  }
 }
}
```

### 3 - End user enters all config values and submits the form

At this point, you have all required auths attached to the solution instance. Now you can make another updateSolutionInstance mutation call and send the config data in one go as shown below:

### updateSolutionInstance API mutation

Pass **solutionInstanceId** and **configValues** in an array as shown below:

```json
mutation {
  updateSolutionInstance(
    input: {
      solutionInstanceId: "aa0axxxx-xxxx-xxxx-xxxx-xxxx9746411f"
      instanceName: "Webhook demo"
      configValues: [
        \{ externalId: "external_slack-message", value: "instance worked fine!" \}
      ]
    }
  ) {
    solutionInstance {
      id
      name
      enabled
      created
      authValues {
        externalId
        authId
      }
      configValues {
        externalId
        value
      }
      workflows {
        edges {
          node {
            id
            sourceWorkflowName
            sourceWorkflowId
            triggerUrl
          }
        }
      }
    }
  }
}
```

The returned data will confirm that the Solution Instance contains the configValues:

```json
{
 "data": {
  "updateSolutionInstance": {
   "solutionInstance": {
    "id": "aa0axxxx-xxxx-xxxx-xxxx-xxxx9746411f",
    "name": "Webhook demo",
    "configValues": [
        \{ "externalId": "external_slack-message", "value": "instance worked fine!" \}
      ]
   }
  }
 }
}
```

### Enable the Solution Instance

Navigate to the relevant **Project** > **Solution instances** section. Here, you'll find the Solution Instance for that End User. Currently, it's disabled to allow you to perform any necessary checks or make changes before activating the Instance.
![ui-instance-disabled](https://tray.ai/documentation/images/platform/embedded/delivery-methods/custom-experience/494c0e1c-b58a0ac1_enable-solution-instance%20%281%29.png)

### User journey map

![api calls step by step : updateSolutionInstance : user journey map](https://tray.ai/documentation/images/platform/embedded/delivery-methods/custom-experience/1JctYP38YEB9q5ojgHb79H_integrationwalkthrough3.drawio%20%282%29.png)

### updateSolutionInstance API mutation

Use the [updateSolutionInstance](https://tray.ai/documentation/developer/embedded-apis/solution-instances#graphql-update-solution-instance) mutation to set `enabled` to 'true'

```js
mutation {
  updateSolutionInstance(
    input: {
      solutionInstanceId: "9e2e9b86-xxxx-xxxx-xxxx-72e84e266d4d"
      instanceName: "Webhook demo"
      authValues: [
        {
          externalId: "external_slack_authenticaion"
          value: "45ec3104-xxxx-xxxx-xxxx-25e810c0c0e8"
        }
      ]
      enabled: true
    }
  ) {
    solutionInstance {
      id
      name
      enabled
      created
      authValues {
        externalId
        authId
      }
    }
  }
}
```

The returned data will confirm that the Solution Instance contains the authValues:

```json
{
 "data": {
  "updateSolutionInstance": {
   "solutionInstance": {
    "id": "9e2e9b86-xxxx-xxxx-xxxx-72e84e266d4d",
    "name": "Webhook demo",
    "enabled": true,
    "created": "2020-10-02T13:06:41.605Z",
    "authValues": [
     \{
      "externalId": "external_slack_authentication",
      "authId": "45ec3104-xxxx-xxxx-xxxx-25e810c0c0e8"
     \}
    ]
   }
  }
 }
}
```

### updateSolutionInstance API sample code

The accordion below shows the function and the API code for this call.

### toggleInstance function code

In the marketplace app, the [utils/tray.js](https://github.com/trayio/nextjs-marketplace-app/blob/e6a1882c2a6b79473bb463ca76be7feb7a0d9e83/utils/tray.js#L95) file shows this function code in use:

```javascript
export const toggleInstance = async (user, instance, enabled, reload) => {
    return new Promise(async (resolve, reject) => {
        console.log('toggleInstance')
        // on error, delete
        await axios.post(/api/toggle-instance, \{
            userToken: user.userToken,
            solutionInstanceId: instance.id,
            enabled,
        \}).then(r => r.data).catch(e => { throw Error(Error toggling solution instance: $\{get(e, 'response.data.message')\}) })
        reload && reload();
        resolve();
    })
}
```

### toggleInstance handler code

In the marketplace app, the [pages/api/toggle-instance.js](https://github.com/trayio/nextjs-marketplace-app/blob/e6a1882c2a6b79473bb463ca76be7feb7a0d9e83/pages/api/toggle-instance.js#L3) file shows the handler function code in use:

```javascript
export default async function handler(req, res) {
  try {
    const \{ userToken, solutionInstanceId, enabled \} = req.body;
    await mutations.toggleSolutionInstance(userToken, solutionInstanceId, enabled);
    res.status(200).json({ message: 'ok' })
  } catch (e) {
    console.error(e);
    res.status(400).json({ message: e.message })
  }
}
```

### toggleSolutionInstance mutation code

In the marketplace app, the [utils/graphql.js](https://github.com/trayio/nextjs-marketplace-app/blob/e6a1882c2a6b79473bb463ca76be7feb7a0d9e83/utils/graphql.js#L275) file shows this API mutation in use:

```javascript
toggleSolutionInstance: async (userToken, solutionInstanceId, enabled) => {
      return request(userToken, gql
            mutation toggle($solutionInstanceId: ID!, $enabled: Boolean!) {
              updateSolutionInstance(input: \{ solutionInstanceId: $solutionInstanceId, enabled: $enabled \}) {
                solutionInstance {
                  id
                  enabled
                }
              }
            }
        , \{
          solutionInstanceId,
          enabled
      \})
    }
```

Please not the warning below when enabling instances through the API:

> **Warning:** If you are auto-enabling the instances for your end users after they create the solution, your code should be configured to delay this call by at least 2 seconds after Create Solution Instance is complete.

## Run and test

### Trigger a run of the Workflow Instance

Now you can paste the **triggerUrl** into your browser address bar and hit 'enter' to trigger a run of the Workflow Instance.

### Debugging the Solution Instance

Navigate to the relevant **Project** > **Solution instances** you will now see that the Solution Instance is enabled and you can click on '**Debug Workflows**' on the solution instance card as shown below:
![ui-instance-click-debug](https://tray.ai/documentation/images/platform/embedded/delivery-methods/custom-experience/494c0e1c-965f6f41_Debugging%20the%20Solution%20Instance%20%281%29.png)
This will open a modal of all the linked workflows of this solution instance. You can the debug the workflow that you want.
![debugging insatnce 3](https://tray.ai/documentation/images/platform/embedded/delivery-methods/custom-experience/4Nynn6le9P8Lis6Bp0yTQQ_debugging%20insatnce%203.png)
This will open a read-only version of the Workflow in a new tab:
![workflow-instance-debug](https://tray.ai/documentation/images/platform/embedded/delivery-methods/custom-experience/494c0e1c-97b8e80f_workflow-instance-debug.png)
As you can see the log data is relevant to this particular End User - reflecting the Slack Channel and message entered in the Custom form.
And the webhook-triggered message has been sent to the Slack channel chosen by the End User in the Custom form:
![msg-to-slack-channel](https://tray.ai/documentation/images/platform/embedded/delivery-methods/custom-experience/494c0e1c-d24dcc0f_msg-to-slack-channel.png)

> **Info:** Please see our documentation on [troubleshooting](https://tray.io/documentation/tray-uac/logs-and-troubleshooting/logs-and-debugging/) for more on debugging, log streaming and error handling.

## List an End User's Solution Instances

![list-users-instances](https://tray.ai/documentation/images/platform/embedded/delivery-methods/custom-experience/494c0e1c-b4ceecd4_list-users-instances.png)

### User journey map

![listSolutionInstanes : user journey map](https://tray.ai/documentation/images/platform/embedded/delivery-methods/custom-experience/1WbywikbiZ8D1Xi3TAohaI_integrationwalkthroughdraft.drawio%20%282%29.png)

### solutionInstances API query

You can pass the **userId** when using the [solutionInstances query (user/master token)](https://tray.ai/documentation/developer/embedded-apis/solution-instances#graphql-get-solution-instances) (if using the master token for this query it is still possible to filter Solution Instances by the owner of the Solution Instance, or by the ID of the Solution which it came from):

```graphql
query ($ownerId: String!){
	viewer {
		solutionInstances(criteria: { owner: $ownerId }) {
			edges {
				node {
					id
					name
					enabled
					owner
					created
					solutionVersionFlags {
						hasNewerVersion
						requiresUserInputToUpdateVersion
						requiresSystemInputToUpdateVersion
					}
					workflows {
						edges {
							node {
								triggerUrl
								id
								sourceWorkflowId
							}
						}
					}
					authValues {
						externalId
						authId
					}
					configValues {
						externalId
						value
					}
				}
				cursor
			}
			pageInfo {
				startCursor
				endCursor
				hasNextPage
				hasPreviousPage
			}
		}
	}
}
```

This will return a list of solution instances.

```javascript
{
  "data": {
    "viewer": {
      "solutionInstances": {
        "edges": [
          {
            "node": {
              "id": "533d48e8-XXXX-XXXX-XXXX-74ff887198d3",
              "name": "SampleApp",
              "enabled": true,
              "owner": "13b3ab9c-XXXX-XXXX-XXXX-c4dd07fbbfa4",
              "created": "2019-12-03T21:11:09.456Z",
              "solutionVersionFlags": \{
                "hasNewerVersion": true,
                "requiresUserInputToUpdateVersion": true,
                "requiresSystemInputToUpdateVersion": false
              \},
              "workflows": {
                "edges": [
                  {
                    "node": \{
                      "triggerUrl": null,
                      "id": "89f8ebeb-XXXX-XXXX-XXXX-be070f3cd0f8",
                      "sourceWorkflowId": "3af6461f-XXXX-XXXX-XXXX-91ada5769f90"
                    \}
                  }
                ]
              },
              "authValues": [
                \{
                  "externalId": "external_slack_authentication",
                  "authId": "dcbc11f1-XXXX-XXXX-XXXX-b7f90dafcf28"
                \}
              ],
              "configValues": []
            },
            "cursor": "NTMzZDQ4ZTgtNjM2Zi00M2FkLTk1ZjItNzRmZjg4NzE5OGQz"
          },
          {
            "node": {
              "id": "fa34cb4c-XXXX-XXXX-XXXX-71205a990ab6",
              "name": "SampleApp",
              "enabled": false,
              "owner": "13b3ab9c-XXXX-XXXX-XXXX-c4dd07fbbfa4",
              "created": "2019-12-03T21:15:55.005Z",
              "solutionVersionFlags": \{
                "hasNewerVersion": true,
                "requiresUserInputToUpdateVersion": true,
                "requiresSystemInputToUpdateVersion": false
              \},
              "workflows": {
                "edges": [
                  {
                    "node": \{
                      "triggerUrl": null,
                      "id": "5a89642e-XXXX-XXXX-XXXX-ddb9daca26b3",
                      "sourceWorkflowId": "3af6461f-XXXX-XXXX-XXXX-91ada5769f90"
                    \}
                  }
                ]
              },
              "authValues": [],
              "configValues": []
            },
            "cursor": "ZmEzNGNiNGMtYjdlMS00NjgxLTliZmUtNzEyMDVhOTkwYWI2"
          }
        ],
        "pageInfo": \{
          "startCursor": "NTMzZDQ4ZTgtNjM2Zi00M2FkLTk1ZjItNzRmZjg4NzE5OGQz",
          "endCursor": "ZmEzNGNiNGMtYjdlMS00NjgxLTliZmUtNzEyMDVhOTkwYWI2",
          "hasNextPage": false,
          "hasPreviousPage": false
        \}
      }
    }
  }
}
```

### solutionInstances API sample code

The accordion below shows the function and the API code for this call.

### listSolutionInstances handler function code

In the marketplace app, the [pages/api/instances.js](https://github.com/trayio/nextjs-marketplace-app/blob/e6a1882c2a6b79473bb463ca76be7feb7a0d9e83/pages/api/instances.js#L5) file shows this function code in use:

```javascript
export default async function handler(req, res) {
  try {
    const { token: userToken } = req.query;
    if (!userToken) {
      throw Error('Expecting a valid token parameter')
    }
    const solutionInstances = await queries.solutionInstances(userToken);
    res.status(200).json(\{ instances: map(solutionInstances, e => e.node) \})
  } catch (e) {
    console.error(e);
    res.status(400).json({ message: e.message })
  }
}
```

### Solution instances query code

In the marketplace app, the [utils/graphql.js](https://github.com/trayio/nextjs-marketplace-app/blob/e6a1882c2a6b79473bb463ca76be7feb7a0d9e83/utils/graphql.js#L86) file shows this API query in use:

```javascript
solutionInstances: async (token) => {
        return request(token, gql
        query solutionInstances {
            viewer {
              solutionInstances {
                edges {
                  node {
                    id
                    name
                    enabled
                    solution {
                      id
                      title
                      description
                      tags
                      customFields {
                        key
                        value
                      }
                    }
                    solutionVersionFlags {
                      hasNewerVersion
                      requiresUserInputToUpdateVersion
                    }
                  }
                  cursor
                }
                pageInfo {
                  hasNextPage
                  hasPreviousPage
                  startCursor
                  endCursor
                }
              }
            }
        }
        ).then(data => get(data, 'viewer.solutionInstances.edges'))
    }
```

### Important note on listing solution instances

> **Info:** The best practice here would be to grab the Solution Instance IDs and Workflow IDs from the createSolutionInstance call. You can do it when users configure the integration the very first time and then store these IDs in your database with the corresponding user.
> This way, you wouldn't have to make this extra graphql query call. We have not followed the best practice in the marketplace app to show how you can achieve this with the API call.

## User edits integration

![editsIntegration customForm](https://tray.ai/documentation/images/platform/embedded/delivery-methods/custom-experience/1outuUMg5b52pGiFZeiYm2_editsIntegration_customForm.png)
Follow these steps to present this experience for end users:

### User journey map

![customForm edits userJourney](https://tray.ai/documentation/images/platform/embedded/delivery-methods/custom-experience/51zyB8p9OEijcTGLDCz71F_customForm_edits_userJourney.png)

### 1 - Launch custom form

This form is launched as the end user clicks on configure button for the solution which would create the solution instance and present this custom form to the user. The form could have sections for adding auths and configs as shown here:
![pathways cutomForm experience](https://tray.ai/documentation/images/platform/embedded/delivery-methods/custom-experience/32CkdbwAgVwdnouGTDogoZ_customForm_ui.png)

### 2 - End user adds a service auth

This should launch the auth-only dialog for this service where the end user can provide auth. You need to repeat the following steps for every service auth that end user adds:

### User journey map

![authOnlyDialog edits userJourney](https://tray.ai/documentation/images/platform/embedded/delivery-methods/custom-experience/75NXbrZ0i9LiuDGSCY8NyU_authOnlyDialog_edits_userJourney.png)

### 1 - Create authorization code

You will need to use [generateAuthorizationCode (using master token)](https://tray.ai/documentation/developer/embedded-apis/users#graphql-create-config-wizard-auth-code) to get the one-time auth-only code for the End User.

### generateAuthorizationCode API mutation

```graphql
mutation {
 generateAuthorizationCode(input: {userId: "fbb96559-xxxx-xxxx-xxxx-5552c2d2fca4"}) {
  authorizationCode
  clientMutationId
 }
}
```

This will return **a one-time use code** which can be used in the **Auth dialog url**:

```json
{
 "data": {
  "generateAuthorizationCode": \{
   "authorizationCode": "b8aab26cxxxxxxxxxxxxxxxxxxxx776c7bba7977",
   "clientMutationId": null
  \}
 }
}
```

### 2 - Compose the Auth dialog URL

Now you can compose the Auth dialog popup URL using the returned **solutionInstanceId** (createSolutionInstance mutation) and \*\*authorizationCode \*\*(generateAuthorizationCode mutation) from the calls above:
`https://embedded.tray.io/external/auth/create/{embeddedId}/{solutionInstanceId}/{externalAuthId}?code={authorizationCode}`
`externalAuthId` is the External ID of the auth in your solution.
![set-external-id](https://tray.ai/documentation/images/platform/embedded/delivery-methods/custom-experience/fd0bc8dd-2e5f4f24_authOnlyDialog.png)
Remember that [embeddedId](https://tray.ai/documentation/platform/embedded/overview/preparing-your-environment/#embedded-id) is the value that you set when configuring your [Config Wizard CSS](https://tray.ai/documentation/platform/embedded/advanced-features/whitelabelling/config-wizard).
Note that your exact URL may differ if you are in a non-US region. Please see the [note above](#notes-on-auth-dialog-url-and-region) for more details.

### Open the URL in browser

![authOnlyDialog ss](https://tray.ai/documentation/images/platform/embedded/delivery-methods/custom-experience/3wxG4ksavHz3HV2zsN8Avl_authOnlyDialog_ss.png)

### Fill the auth data

Enter the auth data for the End User.

### 3 - Update Solution Instance with Authorization

Final step is to use the [Update Solution Instance](https://tray.ai/documentation/developer/embedded-apis/solution-instances#graphql-update-solution-instance) mutation to update the **solutionInstanceId** with authValues for the End User (this requires an access token for your End User as per [Create User Token](https://tray.ai/documentation/developer/embedded-apis/users#graphql-create-user-token))

### updateSolutionInstance API mutation

Use the [updateSolutionInstance](https://tray.ai/documentation/developer/embedded-apis/solution-instances#graphql-update-solution-instance) mutation to set `enabled` to 'true'

```js
mutation {
  updateSolutionInstance(
    input: {
      solutionInstanceId: "9e2e9b86-xxxx-xxxx-xxxx-72e84e266d4d"
      instanceName: "Webhook demo"
      authValues: [
        {
          externalId: "external_slack_authenticaion"
          value: "45ec3104-xxxx-xxxx-xxxx-25e810c0c0e8"
        }
      ]
      enabled: true
    }
  ) {
    solutionInstance {
      id
      name
      enabled
      created
      authValues {
        externalId
        authId
      }
    }
  }
}
```

The returned data will confirm that the Solution Instance contains the authValues:

```json
{
 "data": {
  "updateSolutionInstance": {
   "solutionInstance": {
    "id": "9e2e9b86-xxxx-xxxx-xxxx-72e84e266d4d",
    "name": "Webhook demo",
    "enabled": true,
    "created": "2020-10-02T13:06:41.605Z",
    "authValues": [
     \{
      "externalId": "external_slack_authentication",
      "authId": "45ec3104-xxxx-xxxx-xxxx-25e810c0c0e8"
     \}
    ]
   }
  }
 }
}
```

### 4 - Find all solution instances using this service auth and update them

There are several ways you could do this. Here are two of them:

* Searching your backend database: You could store the auth information for every solution instance and when an auth attached with one solution instance changes, you could query your database to find all other solution instances using this auth and update them one by one as shown in step 3.
* Tagging solution instances: You could tag the solution instances with flags (for example: a tag called 'slack auth only' to all solution instances using slack. If the slack auth is updated, you could retrieve all other solution instances with the 'slack auth only' tag and update them one by one as shown in step 3.

### 3 - End user enters all config values and submits the form

At this point, you have all required auths attached to the solution instance. Now you can make another updateSolutionInstance mutation call and send the config data in one go as shown below:

### updateSolutionInstance API mutation

Pass **solutionInstanceId** and **configValues** in an array as shown below:

```json
mutation {
  updateSolutionInstance(
    input: {
      solutionInstanceId: "aa0axxxx-xxxx-xxxx-xxxx-xxxx9746411f"
      instanceName: "Webhook demo"
      configValues: [
        \{ externalId: "external_slack-message", value: "instance worked fine!" \}
      ]
    }
  ) {
    solutionInstance {
      id
      name
      enabled
      created
      authValues {
        externalId
        authId
      }
      configValues {
        externalId
        value
      }
      workflows {
        edges {
          node {
            id
            sourceWorkflowName
            sourceWorkflowId
            triggerUrl
          }
        }
      }
    }
  }
}
```

The returned data will confirm that the Solution Instance contains the configValues:

```json
{
 "data": {
  "updateSolutionInstance": {
   "solutionInstance": {
    "id": "aa0axxxx-xxxx-xxxx-xxxx-xxxx9746411f",
    "name": "Webhook demo",
    "configValues": [
        \{ "externalId": "external_slack-message", "value": "instance worked fine!" \}
      ]
   }
  }
 }
}
```

### Note on updating Solution Instances

When **you** (not end user) publish a new version of a **Solution**, there are two types of updates that would happen to all Solution Instances linked to this Solution:

* A 'lazy' update (no new config or auths are required)
* A 'breaking changes' update (i.e. requires new auth and/or config data) whereby you will have to notify End Users to edit their integrations as above
  For more details, please see our guide to Updating Solutions and Instances.
  ![api calls breakdown : update solution instance : user journey scenarios map](https://tray.ai/documentation/images/platform/embedded/delivery-methods/custom-experience/B8KXoziPjH6Qfhhx6XBFL_integrationwalkthroughUpdateSolution.drawio.png)

## User deletes integration

![api calls breakdown : deleteInstance : ui image](https://tray.ai/documentation/images/platform/embedded/delivery-methods/custom-experience/73b66ALFE9kokDLwecDEFF_deleteInstance_apicalls.png)

### Delete solution instance

Users may want to delete the instance they no longer wish to use and you can achieve it by following the steps below:

### User journey map

![api callls breakdown : delete solution instance : user journey map](https://tray.ai/documentation/images/platform/embedded/delivery-methods/custom-experience/5ttzaCQhuN4bAmrls3XRBK_integrationwalkthroughdraft.drawio%20%283%29.png)

### deleteSolutionInstance API mutation

You can pass the **solutionId** to [deleteSolutionInstance (user token)](https://tray.ai/documentation/developer/embedded-apis/solution-instances#graphql-delete-solution-instance) to delete a Solution Instance:

```json
mutation ($solutionInstanceId: ID!){
  removeSolutionInstance(input: {solutionInstanceId: $solutionInstanceId}) {
   clientMutationId
 }
}
```

The result will return the clientMutationId:

```json
{
  "data": {
    "removeSolutionInstance": {
      "clientMutationId": "687xxxxxxxxxxx931"
    }
  }
}
```

### deleteSolutionInstance API mutation sample code

The accordion below shows the function and the API code for this call.

### disconnectSolution function code

In the marketplace app, the [utils/tray.js](https://github.com/trayio/nextjs-marketplace-app/blob/e6a1882c2a6b79473bb463ca76be7feb7a0d9e83/utils/tray.js#L83) file shows this function code in use:

```js
export const disconnectSolution = async (user, instance, reload) => {
    return new Promise(async (resolve, reject) => {
        // on error, delete
        await axios.post(/api/delete-instance, \{
            userToken: user.userToken,
            solutionInstanceId: instance.id,
        \}).then(r => r.data).catch(e => { throw Error(Error disconnecting solution instance: $\{get(e, 'response.data.message')\}) })
        reload && reload();
        resolve();
    })
}
```

### delete-instance handler function code

In the marketplace app, the [pages/api/delete-instance.js](https://github.com/trayio/nextjs-marketplace-app/blob/e6a1882c2a6b79473bb463ca76be7feb7a0d9e83/pages/api/delete-instance.js#L3) file shows the handler function code in use:

```js
export default async function handler(req, res) {
  try {
    const \{ userToken, solutionInstanceId \} = req.body;
    await mutations.deleteSolutionInstance(userToken, solutionInstanceId);
    res.status(200).json({ message: 'ok' })
  } catch (e) {
    console.error(e);
    res.status(400).json({ message: e.message })
  }
}
```

### deleteSolutionInstance mutation sample code

In the marketplace app, the [utils/graphql.js](https://github.com/trayio/nextjs-marketplace-app/blob/e6a1882c2a6b79473bb463ca76be7feb7a0d9e83/utils/graphql.js#L263) file shows this API mutation in use:

```js
deleteSolutionInstance: async (userToken, solutionInstanceId) => {
      return request(userToken, gql
            mutation delete($solutionInstanceId: ID!) {
              removeSolutionInstance(input: { solutionInstanceId: $solutionInstanceId }) {
                clientMutationId
              }
            }
        , {
          solutionInstanceId
      })
    }
```
