# The Standard Experience (Config Wizard)

This page will introduce you to all the API calls necessary in order to register an End User and allow them to click on an available solution to activate their own instance.

This page will introduce you to all the API calls necessary in order to register an End User and allow them to click on an available solution to activate their own instance.

* Each API call will need the **correct token (master or user) passed as a bearer**. This is done largely automatically in our Insomnia collection (once you add your master token to the environment) but will need to be entered manually when using the GraphQL playground.
* You will be able to **simulate an End User clicking to configure a Solution Instance** by deploying the Integration Marketplace App and completing the setup of a Solution Instance for that End User.
* You will then be able to **test the Solution Instance** by pasting its webhook URL into your browser. As it is a webhook-triggered workflow, this will trigger a **run of the End User's Workflow Instance**.
* Finally, you can then login to the Tray Embedded UI to find the End User's Solution and Workflow Instances, to see how **debugging** works.

## Notes on the user journey

Note that, while there may be a fairly 'linear' process for when an End User first configures an integration for their use, **the API calls by your app are not necessarily made in a strict order**.
For example, an End User may need to **edit their settings for an integration at any point** after they have registered.
You may also wish to **list available integrations before a user has registered**.
For each section below we have included a user journey diagram such as the following for when an End User logs in and available integrations are displayed:
![api calls step by step : listSolutions : user journey map](https://tray.ai/documentation/images/platform/embedded/delivery-methods/config-wizard/6UACqf9IzRAsmxTcgIuc5P_integrationwalkthrough1.drawio%20%285%29.png)

## Notes on Config Wizard URL and region

When initiating the Config Wizard for your End Users, you will need to compose the Config Wizard URL as such
`https://embedded.tray.io/external/solutions/${embeddedId}/configure/${solutionInstanceId}?code=${authorizationCode}`
This URL will vary depending on your region.
If you are in the EU region, the URL would be:
`https://embedded.eu1.tray.io/external/solutions/${embeddedId}/configure/${solutionInstanceId}?code=${authorizationCode}`
If you are in the APAC region, the URL would be:
`https://embedded.ap1.tray.io/external/solutions/${embeddedId}/configure/${solutionInstanceId}?code=${authorizationCode}`

## Notes on using the Integration Marketplace app

> **Info:** The sections below also contain relevant pieces of code from [Tray's Integration Marketplace App](https://github.com/trayio/nextjs-marketplace-app) on how to use each API call. You can [deploy](https://tray.ai/documentation/platform/embedded/overview/integration-marketplace-app) this application and use it as an example for creating your own application.

## Register a user

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

### User journey map

![createExternalUser : user journey diagram](https://tray.ai/documentation/images/platform/embedded/delivery-methods/config-wizard/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/config-wizard/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/config-wizard/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/config-wizard/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/config-wizard/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

![configWizardExp](https://tray.ai/documentation/images/platform/embedded/delivery-methods/config-wizard/7BIM0ROV853SaTUoTM8Px5_configWizardExp.png)

### Create solution instance

### User journey map

![api calls step by step : createSolutionInstance : user journey map](https://tray.ai/documentation/images/platform/embedded/delivery-methods/config-wizard/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/config-wizard/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'))
    }
```

> **Warning:** At this stage, there are no **authValues** or **configValues**. They will be populated when the End User runs the Config Wizard.

### User journey map

![api calls step by step : generateAuthCode : user journey map](https://tray.ai/documentation/images/platform/embedded/delivery-methods/config-wizard/4I36hi9JxOrfpHBkqSe1zi_integrationwalkthrough2.drawio%20%283%29.png)

### 1 - Create Config Wizard 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 Config Wizard 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 **Config Wizard url**:

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

### generateAuthorizationCode API sample code

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

### createAuthCode handler function code

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

```javascript
export default async function handler(req, res) {
  try {
    const { jwt: userJWT } = req.query;
    const masterToken = await getMasterToken(req.query);
    const \{ user, claim \} = await validateJsonWebToken(masterToken, userJWT);
    if (!user) {
        throw Error('Invalid user claim')
    }
    console.log('Creating auth code for user', user)
    const code = await mutations.createAuthorizationCode(user.id);
    res.status(200).json({ code })
  } catch (e) {
    console.error(e);
    res.status(400).json({ message: e.message })
  }
}
```

### createAuthorizationCode mutation code

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

```javascript
createAuthorizationCode: async (userId, token = process.env.MASTER_TOKEN) => {
      return request(token, gql
            mutation code($userId: ID!) {
              generateAuthorizationCode(input: {userId: $userId}) {
                authorizationCode
              }
            }
        , \{
          userId,
      \}).then(data => get(data, 'generateAuthorizationCode.authorizationCode'))    
    }
```

### 2 - Compose the Config Wizard URL

Now you can compose the url using the returned **solutionInstanceId** and **authorizationCode** from the calls above:
`https://embedded.tray.io/external/solutions/${embeddedId}/configure/${solutionInstanceId}?code=${authorizationCode}`
Note that your exact URL may differ if you are in a non-US region. Please see the [note above](#notes-on-config-wizard-url-and-region) for more details.
Remember that [embeddedId](https://tray.ai/documentation/platform/embedded/overview/preparing-your-environment) is the value that you set when configuring your [Config Wizard CSS](https://tray.ai/documentation/platform/embedded/advanced-features/whitelabelling/config-wizard).

> **Info:** You can remove the Tray domain and [whitelabel](https://tray.io/documentation/tray-uac/embedded-integrations/css-and-whitelabelling/intro/) the Config Wizard url. If you have done so then `embedded.tray.io` will be replaced with e.g. `acme.integration-configuration.com`

### Open the URL in browser

When you enter the url in your browser, you should then be presented with the Config Wizard:
![url-in-browser-config-wizard](https://tray.ai/documentation/images/platform/embedded/delivery-methods/config-wizard/494c0e1c-709e19c6_url-in-browser-config-wizard.png)

### Fill the config wizard

Enter the auth and Config Data for the End User.

Note also that the End User is presented with the public url for their workflow instance. This can be **hidden** from the End User in the Solution Editor, and you can retrieve it as the **triggerUrl** when creating or querying the Solution Instance:
![config-wizard-solution-instance-trigger-url](https://tray.ai/documentation/images/platform/embedded/delivery-methods/config-wizard/494c0e1c-6e623f11_config-wizard-solution-instance-trigger-url.png)

> **Warning:** Since you have activated the pop-up directly via a URL, and not as a pop-up in your application, the Config Wizard will hang when you click 'Finish' on the final screen. Hence, you need to manually close the window here.

### 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/config-wizard/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/config-wizard/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/config-wizard/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/config-wizard/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/config-wizard/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 Config Wizard.
And the webhook-triggered message has been sent to the Slack channel chosen by the End User in the Config Wizard:
![msg-to-slack-channel](https://tray.ai/documentation/images/platform/embedded/delivery-methods/config-wizard/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/config-wizard/494c0e1c-b4ceecd4_list-users-instances.png)

### User journey map

![listSolutionInstanes : user journey map](https://tray.ai/documentation/images/platform/embedded/delivery-methods/config-wizard/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

![api calls breakdown : update solution instance : ui look](https://tray.ai/documentation/images/platform/embedded/delivery-methods/config-wizard/4fMBt2B3STo2MperNfb0SL_editsIntegration_configWizard.png)
Follow these steps to present this experience for end users:

### User Journey Map

![ap calls breakdown : EditConfig : user journey](https://tray.ai/documentation/images/platform/embedded/delivery-methods/config-wizard/3TU3xfsIYLGsEcztuhoCZT_iw_EditConfig.drawio.png)

### updateInstance function code breakdown

In the marketplace app, the [utils/tray.js](https://github.com/trayio/nextjs-marketplace-app/blob/e6a1882c2a6b79473bb463ca76be7feb7a0d9e83/utils/tray.js#L62) file shows the `updateInstance` function. Here's a breakdown of this function:
![updateIntsance code breakdown](https://tray.ai/documentation/images/platform/embedded/delivery-methods/config-wizard/4zMcrE7CA5JZiRfjexeM65_updateIntsance_code_breakdown.png)
Note that here you don't need to enable instance (like we did in [connectSolution](https://github.com/trayio/nextjs-marketplace-app/blob/e6a1882c2a6b79473bb463ca76be7feb7a0d9e83/utils/tray.js#L17)) as the end user is editing an existing instance.

### 1 - Create Config Wizard 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 Config Wizard 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 **Config Wizard url**:

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

### generateAuthorizationCode API sample code

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

### createAuthCode handler function code

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

```javascript
export default async function handler(req, res) {
  try {
    const { jwt: userJWT } = req.query;
    const masterToken = await getMasterToken(req.query);
    const \{ user, claim \} = await validateJsonWebToken(masterToken, userJWT);
    if (!user) {
        throw Error('Invalid user claim')
    }
    console.log('Creating auth code for user', user)
    const code = await mutations.createAuthorizationCode(user.id);
    res.status(200).json({ code })
  } catch (e) {
    console.error(e);
    res.status(400).json({ message: e.message })
  }
}
```

### createAuthorizationCode mutation code

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

```javascript
createAuthorizationCode: async (userId, token = process.env.MASTER_TOKEN) => {
      return request(token, gql
            mutation code($userId: ID!) {
              generateAuthorizationCode(input: {userId: $userId}) {
                authorizationCode
              }
            }
        , \{
          userId,
      \}).then(data => get(data, 'generateAuthorizationCode.authorizationCode'))    
    }
```

### 2 - Compose the Config Wizard URL

> **Info:** In the marketplace app, the `updateInstance` function configures the URL and calls the `openConfigWizard` function. Check the breakdown of `updateInstance` function as shown above.

First compose the URL using the returned **solutionInstanceId** and **authorizationCode** from the calls above:
`https://embedded.tray.io/external/solutions/${embeddedId}/configure/${solutionInstanceId}?code=${authorizationCode}`
Note that your exact URL may differ if you are in a non-US region. Please see the [note above](#notes-on-config-wizard-url-and-region) for more details.
Remember that [embeddedId](https://tray.ai/documentation/platform/embedded/overview/preparing-your-environment) is the value that you set when configuring your [Config Wizard CSS](https://tray.ai/documentation/platform/embedded/advanced-features/whitelabelling/config-wizard).

> **Info:** You can remove the Tray domain and [whitelabel](https://tray.io/documentation/tray-uac/embedded-integrations/css-and-whitelabelling/intro/) the Config Wizard url. If you have done so then `embedded.tray.io` will be replaced with e.g. `acme.integration-configuration.com`

### Open the URL in browser

When you enter the url in your browser, you should then be presented with the Config Wizard:
![url-in-browser-config-wizard](https://tray.ai/documentation/images/platform/embedded/delivery-methods/config-wizard/494c0e1c-709e19c6_url-in-browser-config-wizard.png)

### Fill the config wizard

Enter the auth and Config Data for the End User.

Note also that the End User is presented with the public url for their workflow instance. This can be **hidden** from the End User in the Solution Editor, and you can retrieve it as the **triggerUrl** when creating or querying the Solution Instance:
![config-wizard-solution-instance-trigger-url](https://tray.ai/documentation/images/platform/embedded/delivery-methods/config-wizard/494c0e1c-6e623f11_config-wizard-solution-instance-trigger-url.png)

> **Warning:** Since you have activated the pop-up directly via a URL, and not as a pop-up in your application, the Config Wizard will hang when you click 'Finish' on the final screen. Hence, you need to manually close the window here.

### 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/config-wizard/B8KXoziPjH6Qfhhx6XBFL_integrationwalkthroughUpdateSolution.drawio.png)

## User deletes integration

![api calls breakdown : deleteInstance : ui image](https://tray.ai/documentation/images/platform/embedded/delivery-methods/config-wizard/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/config-wizard/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
      })
    }
```
