public async Task <IActionResult> Config([FromBody] IntegrationSetupRequest request)
        {
            request.AdditionalProperties.TryGetValue("voicify-app", out var voicifyAppId);
            request.AdditionalProperties.TryGetValue("quick-base-token", out var quickbaseToken);
            request.AdditionalProperties.TryGetValue("quick-base-realm", out var quickbaseRealm);

            // validate inputs
            if (string.IsNullOrEmpty(voicifyAppId))
            {
                return(BadRequest(new[] { "You must select a Voicify app." }));
            }
            if (string.IsNullOrEmpty(quickbaseToken))
            {
                return(BadRequest(new[] { "You must provide a user token for Quick Base." }));
            }
            if (string.IsNullOrEmpty(quickbaseRealm))
            {
                return(BadRequest(new[] { "You must provide a Quick Base realm." }));
            }
            var voicifyConfig = new Configuration
            {
                BasePath      = "https://cms.voicify.com",
                DefaultHeader = new Dictionary <string, string>
                {
                    { "Authorization", $"Bearer {request.AccessToken}" }
                }
            };

            using var client = new HttpClient();
            var appProvider   = new QuickBaseAppProvider(client, quickbaseRealm, quickbaseToken);
            var tableProvider = new QuickBaseTableProvider(client, quickbaseRealm, quickbaseToken);
            var fieldProvider = new QuickBaseFieldProvider(client, quickbaseRealm, quickbaseToken);
            var appApi        = new ApplicationApi(voicifyConfig);
            var userApi       = new UserApi(voicifyConfig);
            var webhookApi    = new WebhookApi(voicifyConfig);
            var orgApi        = new OrganizationApi(voicifyConfig);


            // get voicify org
            var orgs = await orgApi.GetForUserAsync();

            var org = orgs.FirstOrDefault(o => o.Id == request.OrganizationId);

            // get voicify app
            var app = await appApi.FindApplicationAsync(voicifyAppId);

            // create quick base app
            var appResult = await appProvider.CreateApp(new NewQuickBaseAppRequest
            {
                AssignToken = true,
                Description = app.Description,
                Name        = app.Name
            });

            if (appResult.ResultType != ResultType.Ok)
            {
                return(BadRequest(appResult.Errors));
            }

            // create tables
            var faqTableResult = await tableProvider.CreateTable(appResult.Data.Id, new NewTableRequest
            {
                Name             = "Questions and Answers",
                Description      = "Free form question and answer options",
                PluralRecordName = "Questions and Answers",
                SingleRecordName = "Question and Answer"
            });

            if (faqTableResult.ResultType != ResultType.Ok)
            {
                return(BadRequest(faqTableResult.Errors));
            }
            var requestTableResult = await tableProvider.CreateTable(appResult.Data.Id, new NewTableRequest
            {
                Name             = "Conversational Request Data",
                Description      = "Data from the requests made by users to the voice/bot application",
                PluralRecordName = "Requests",
                SingleRecordName = "Request"
            });

            if (requestTableResult.ResultType != ResultType.Ok)
            {
                return(BadRequest(requestTableResult.Errors));
            }

            // create fields for tables
            var results = await Task.WhenAll(new List <Task <Result <(QuickBaseField Field, string TableId)> > >
            {
                fieldProvider.CreateField(faqTableResult.Data.Id, new NewFieldRequest
                {
                    Label = "question"
                }),
                fieldProvider.CreateField(faqTableResult.Data.Id, new NewFieldRequest
                {
                    Label = "answer"
                }),
                fieldProvider.CreateField(faqTableResult.Data.Id, new NewFieldRequest
                {
                    Label = "foregroundImageUrl"
                }),
                fieldProvider.CreateField(faqTableResult.Data.Id, new NewFieldRequest
                {
                    Label = "followUpPrompt"
                }),
                fieldProvider.CreateField(faqTableResult.Data.Id, new NewFieldRequest
                {
                    Label = "nextItemRecordIds"
                }),


                fieldProvider.CreateField(requestTableResult.Data.Id, new NewFieldRequest
                {
                    Label = "applicationId"
                }),
                fieldProvider.CreateField(requestTableResult.Data.Id, new NewFieldRequest
                {
                    Label = "requestDate"
                }),
                fieldProvider.CreateField(requestTableResult.Data.Id, new NewFieldRequest
                {
                    Label = "platform"
                }),
                fieldProvider.CreateField(requestTableResult.Data.Id, new NewFieldRequest
                {
                    Label = "requestId"
                }),
                fieldProvider.CreateField(requestTableResult.Data.Id, new NewFieldRequest
                {
                    Label = "userId"
                }),
                fieldProvider.CreateField(requestTableResult.Data.Id, new NewFieldRequest
                {
                    Label = "sessionId"
                }),
                fieldProvider.CreateField(requestTableResult.Data.Id, new NewFieldRequest
                {
                    Label = "slots"
                }),
                fieldProvider.CreateField(requestTableResult.Data.Id, new NewFieldRequest
                {
                    Label = "contentItemId"
                }),
                fieldProvider.CreateField(requestTableResult.Data.Id, new NewFieldRequest
                {
                    Label = "featureTypeId"
                }),
            });


            // create API user in voicify
            var apiUser = await userApi.CreateApiUserAsync(new NewApiUserRequest(request.OrganizationId));

            // create token with api user credentials
            var token = TokenConvert.SerializeEncryptedToken(new WebhookTokenModel
            {
                VoicifyOrganizationId       = org.Id,
                VoicifyOrganizationSecret   = org.Secret,
                VoicifyApiUserName          = apiUser.Username,
                VoicifyApiUserSecret        = apiUser.Password,
                QuickBaseToken              = quickbaseToken,
                QuickBaseRealm              = quickbaseRealm,
                VoicifyApplicationId        = voicifyAppId,
                QuickBaseAppId              = appResult.Data.Id,
                QuickBaseFaqTableId         = faqTableResult.Data.Id,
                QuickBaseRequestTableId     = requestTableResult.Data.Id,
                QuickBaseRequestTableMatrix = results.Where(r => r.Data.TableId == requestTableResult.Data.Id)
                                              .ToDictionary(
                    d => d.Data.Field.Label,
                    d => d.Data.Field.Id.ToString()),
                QuickBaseFaqTableMatrix = results.Where(r => r.Data.TableId == faqTableResult.Data.Id)
                                          .ToDictionary(
                    d => d.Data.Field.Label,
                    d => d.Data.Field.Id.ToString())
            }, _config.GetValue <string>("EncodingKey") ?? "whoops");

            // create webhook in voicify
            var webhook = await webhookApi.CreateWebhookAsync(request.OrganizationId,
                                                              new NewWebhookRequest(
                                                                  title : $"{app?.Name ?? "App"} - Quick Base Request Event",
                                                                  description : "Creates event records in Quick Base when a request is received through Voicify. Use this token for Quick Base pipelines as well",
                                                                  url : "https://quick-base-voicify-sync.azurewebsites.net/api/voicify/contentHit",
                                                                  webhookTypeId : "53b40ef2-769c-46e6-bc99-c709e7600c03", // Content Hit Event webhook type
                                                                  accessToken : token
                                                                  ));

            // add webhook to app
            var appWebhook = await appApi.AddWebhookAsync(voicifyAppId, webhook.Id, new WebhookParametersRequest(values : new Dictionary <string, string>(), userDefinedParameters : new Dictionary <string, string>()));


            return(Ok());
        }