PA Engine API

  • SECTIONS
  • Overview
  • API Definition
  • API Documentation
  • SDK Libraries
  • Notebooks
  • FactSet Connectors
  • Code Snippet
  • Changelog
Overview

FactSet's PA Engine API enables programmatic and comprehensive access to Portfolio Analysis. This flexible REST API allows users and developers to access and utilize the full suite of portfolio analysis features, historically available only via graphic user interfaces (GUI).

Go beyond the GUI:

  • Effortless Report Creation: Generate any portfolio analysis report imaginable, including multi-asset class portfolio index composition, characteristics, ESG, performance, attribution, peer analysis, predicted risk, and more.
  • Programmatic Control: Craft reports tailored to your specific needs without relying on the graphic user interface. Override existing report options or leverage pre-built templates for efficient analysis.
  • Customization Power: For users with existing Portfolio Analytics access, the Components Manager endpoints allow the creation of custom report shells perfectly aligned with your investment strategies.

The PA Engine API empowers you to:

  • Simplify Workflows: Integrate robust portfolio analysis functionalities seamlessly into your existing applications for a unified user experience.
  • Automate Reporting: Automate report generation and delivery based on user-defined parameters, saving valuable time and resources.
  • Enhanced Scalability: Programmatically analyze large and complex portfolios with ease, ensuring efficient results.
  • BI Integration: Leverage connectors to integrate FactSet's portfolio analysis data into popular Business Intelligence (BI) tools like Power BI, QlikSense, and Snowflake. This enables further data exploration, visualization, and deeper portfolio insights within your preferred BI environment.
  • Hands-on Learning: Explore the included Python notebook example to gain practical experience with the PA Engine API. This notebook demonstrates how to interact with the API programmatically, without relying on the user interface, to build an end-to-end portfolio analysis workflow.

Next Steps:

Explore the PA Engine API documentation for a detailed breakdown of available endpoints, request parameters, and response formats.

Leverage code samples to jumpstart your integration efforts and unlock the power of programmatic access to the portfolio analysis.

Please see the Portfolio Analytics API OA Page for more details.

(Please note our Tableau connector has now been retired.)

API Definition
SDK Libraries
Notebooks
Decoupling Analytics from the Workstation
Portfolio Commentary
Code Snippet
Building and running a single calculation
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using FactSet.Protobuf.Stach.Extensions;
using FactSet.SDK.PAEngine.Api;
using FactSet.SDK.PAEngine.Client;
using FactSet.SDK.PAEngine.Model;
using FactSet.SDK.Utils.Authentication;

namespace FactSet.AnalyticsAPI.PAEngineESDK.Example
{
    public class PAEngineSingleUnitExample
    {
        private static readonly string BasePath = Environment.GetEnvironmentVariable("FACTSET_HOST");
        private static readonly string UserName = Environment.GetEnvironmentVariable("FACTSET_USERNAME");
        private static readonly string Password = Environment.GetEnvironmentVariable("FACTSET_PASSWORD");
        private const string PADocument = "pa3_documents:/pa_api_default_document-rbics";
        private const string ComponentName = "Multiple Portfolios";
        private const string ComponentCategory = "Exposures & Characteristics / Exposures & Characteristics";
        private const string Portfolio = "LION:100D-GB";
        private const string Benchmark = "LION:OEF-US";
        private const string startDate = "";
        private const string endDate = "20240508";
        private const string frequency = "Single";
        private const string currency = "USD";
        private const string holdings = "B&H";
        private const string PricingSourceName = "MSCI - Gross";
        private const string PricingSourceCategory = "MSCI";
        private const string PricingSourceDirectory = "Equity";
        private static SDK.PAEngine.Client.Configuration _engineApiConfiguration;

        public static async Task Main(string[] args)
        {
            try
            {
                var calculationParameters = await GetPaCalculationParametersAsync();

                var calculationApi = new PACalculationsApi(await GetApiConfigurationAsync());

                var calculationResponse = calculationApi.PostAndCalculateWithHttpInfo(null, null, calculationParameters);
                // Comment the above line and uncomment the below lines to add cache control configuration. Results are by default cached for 12 hours; Setting max-stale=300 will fetch a cached result which is at max 5 minutes older.
                //var cacheControl = "max-stale=0";
                //var calculationResponse = calculationApi.PostAndCalculateWithHttpInfo(null, cacheControl, calculationParameters);

                if (calculationResponse.StatusCode == HttpStatusCode.Created)
                {
                    ObjectRoot result = (ObjectRoot)calculationResponse.Data.Response;
                    PrintResult(result);
                    return;
                }

                CalculationStatusRoot status = (CalculationStatusRoot)calculationResponse.Data.Response;
                var calculationId = status.Data.Calculationid;
                Console.WriteLine("Calculation Id: " + calculationId);

                ApiResponse<CalculationStatusRoot> getStatusResponse = null;

                while (status.Data.Status == CalculationStatus.StatusEnum.Queued || status.Data.Status == CalculationStatus.StatusEnum.Executing)
                {
                    if (getStatusResponse != null)
                    {
                        if (getStatusResponse.Headers.ContainsKey("Cache-Control"))
                        {
                            var maxAge = getStatusResponse.Headers["Cache-Control"][0];
                            if (string.IsNullOrWhiteSpace(maxAge))
                            {
                                Console.WriteLine("Sleeping for 2 seconds");
                                // Sleep for at least 2 seconds.
                                Thread.Sleep(2000);
                            }
                            else
                            {
                                var age = int.Parse(maxAge.Replace("max-age=", ""));
                                Console.WriteLine($"Sleeping for {age} seconds");
                                Thread.Sleep(age * 1000);
                            }
                        }
                    }

                    getStatusResponse = calculationApi.GetCalculationStatusByIdWithHttpInfo(calculationId);
                    status = getStatusResponse.Data;
                }
                Console.WriteLine("Calculation Completed");


                foreach (var calculation in status.Data.Units)
                {
                    if (calculation.Value.Status == CalculationUnitStatus.StatusEnum.Success)
                    {
                        var resultResponse = calculationApi.GetCalculationUnitResultByIdWithHttpInfo(calculationId, calculation.Key);
                        PrintResult(resultResponse.Data);
                    }
                    else
                    {
                        Console.WriteLine($"Calculation Unit Id : {calculation.Key} Failed!!!");
                        Console.WriteLine($"Error message : {calculation.Value.Errors?.FirstOrDefault()?.Detail}");
                    }
                }

                Console.ReadKey();
            }
            catch (ApiException e)
            {
                Console.WriteLine($"Status Code: {e.ErrorCode}");
                Console.WriteLine($"Reason : {e.Message}");
                Console.WriteLine(e.StackTrace);
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                throw;
            }
        }
        private static async Task<SDK.PAEngine.Client.Configuration> GetApiConfigurationAsync()
        {
            //Supported authentication methods are below,
            //choose one that satisfies your use case.

            ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
            if (_engineApiConfiguration != null)
            {
                return _engineApiConfiguration;
            }

            #region BasicAuthentication

            /* Basic authentication: FactSetApiKey */
            // See https://github.com/FactSet/enterprise-sdk#api-key
            // for information how to create an API key

            _engineApiConfiguration = new SDK.PAEngine.Client.Configuration
            {
                BasePath = BasePath,
                Username = UserName,
                Password = Password,
            };

            #endregion

            // Uncomment below lines for adding the proxy configuration
            //System.Net.WebProxy webProxy = new System.Net.WebProxy("http://myProxyUrl:80/");
            //webProxy.Credentials = System.Net.CredentialCache.DefaultCredentials;
            //_engineApiConfiguration.Proxy = webProxy;

            #region FactSetOAuth2

            /* (Preferred) OAuth 2.0: FactSetOAuth2 */
            // See https://github.com/FactSet/enterprise-sdk#oauth-20
            // for information on how to create the app-config.json file
            // See https://github.com/FactSet/enterprise-sdk-utils-dotnet#authentication
            // for more information on using the ConfidentialClient class

            //Uncomment below lines and comment the above lines(region:BasicAuthentication) for FactSetOAuth2 Authentication
            //ConfidentialClient confidentialClient = await ConfidentialClient.CreateAsync("");
            //_engineApiConfiguration = new FactSet.SDK.PAEngine.Client.Configuration();
            //_engineApiConfiguration.OAuth2Client = confidentialClient;

            #endregion

            return _engineApiConfiguration;
        }

        private static async Task<PACalculationParametersRoot> GetPaCalculationParametersAsync()
        {
            var componentsApi = new ComponentsApi(await GetApiConfigurationAsync());

            var componentsResponse = componentsApi.GetPAComponents(PADocument);

            var paComponentId = componentsResponse.Data.FirstOrDefault(component => (component.Value.Name == ComponentName && component.Value.Category == ComponentCategory)).Key;
            Console.WriteLine($"PA Component Id : {paComponentId}");

            var pricingSourcesApi = new PricingSourcesApi(await GetApiConfigurationAsync());

            var pricingSourcesResponse = pricingSourcesApi.GetPAPricingSources(PricingSourceName, PricingSourceCategory, PricingSourceDirectory);

            var paPricingSourceId = pricingSourcesResponse.Data.FirstOrDefault(pricingSource => (pricingSource.Value.Name == PricingSourceName && pricingSource.Value.Category == PricingSourceCategory && pricingSource.Value.Directory == PricingSourceDirectory)).Key;
            Console.WriteLine($"PA Pricing Source Id : {paPricingSourceId}");

            var paAccountIdentifier = new PAIdentifier(Portfolio, holdings);
            var paAccounts = new List<PAIdentifier> { paAccountIdentifier };
            var paBenchmarkIdentifier = new PAIdentifier(Benchmark);
            var paBenchmarks = new List<PAIdentifier> { paBenchmarkIdentifier };
            var paDates = new PADateParameters(endDate, frequency, startDate);

            var paPortfolioPricingSources = new List<PACalculationPricingSource> { new PACalculationPricingSource(id: paPricingSourceId) };

            var paDataSources = new PACalculationDataSources(portfoliopricingsources: paPortfolioPricingSources, useportfoliopricingsourcesforbenchmark: true);

            var paCalculation = new PACalculationParameters(componentid: paComponentId, accounts: paAccounts, benchmarks: paBenchmarks, datasources: paDataSources, currencyisocode: currency, dates: paDates);

            var calculationParameters = new PACalculationParametersRoot
            {
                Data = new Dictionary<string, PACalculationParameters> { { "1", paCalculation } }
            };

            return calculationParameters;
        }

        private static void PrintResult(ObjectRoot result)
        {
            Console.WriteLine("Calculation Result");

            // converting the data to Package object
            var stachBuilder = StachExtensionFactory.GetRowOrganizedBuilder();
            var stachExtension = stachBuilder.SetPackage(result.Data).Build();
            // To convert package to 2D tables.
            var tables = stachExtension.ConvertToTable();

            Console.WriteLine(tables[0]);
        }
    }
}
Building and running multiple calculations
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using FactSet.Protobuf.Stach.Extensions;
using FactSet.SDK.PAEngine.Api;
using FactSet.SDK.PAEngine.Client;
using FactSet.SDK.PAEngine.Model;
using FactSet.SDK.Utils.Authentication;

namespace FactSet.AnalyticsAPI.PAEngineESDK.Example
{
    public class PAEngineMultipleUnitExample
    {
        private static readonly string BasePath = Environment.GetEnvironmentVariable("FACTSET_HOST");
        private static readonly string UserName = Environment.GetEnvironmentVariable("FACTSET_USERNAME");
        private static readonly string Password = Environment.GetEnvironmentVariable("FACTSET_PASSWORD");
        private const string PADocument = "pa3_documents:/pa_api_default_document-rbics";
        private const string ComponentName = "Multiple Portfolios";
        private const string ComponentCategory = "Exposures & Characteristics / Exposures & Characteristics";
        private const string Portfolio1 = "LION:100D-GB";
        private const string Portfolio2 = "LION:FXI-US";
        private const string Benchmark = "LION:OEF-US";
        private const string startDate1 = "20240507";
        private const string startDate2 = "20240507";
        private const string endDate = "20240508";
        private const string frequency1 = "Single";
        private const string frequency2 = "Daily";
        private const string holdings = "B&H";
        private const string currency = "USD";
        private const string PricingSourceName = "MSCI - Gross";
        private const string PricingSourceCategory = "MSCI";
        private const string PricingSourceDirectory = "Equity";
        private static SDK.PAEngine.Client.Configuration _engineApiConfiguration;

        public static async Task Main(string[] args)
        {
            try
            {
                var calculationParameters = await GetPaCalculationParametersAsync();

                var calculationApi = new PACalculationsApi(await GetApiConfigurationAsync());

                var calculationResponse = calculationApi.PostAndCalculateWithHttpInfo(null, null, calculationParameters);
                // Comment the above line and uncomment the below lines to add cache control configuration. Results are by default cached for 12 hours; Setting max-stale=300 will fetch a cached result which is at max 5 minutes older.
                //var cacheControl = "max-stale=0";
                //var calculationResponse = calculationApi.PostAndCalculateWithHttpInfo(null, cacheControl, calculationParameters);

                if (calculationResponse.StatusCode == HttpStatusCode.Created)
                {
                    ObjectRoot result = (ObjectRoot)calculationResponse.Data.Response;
                    PrintResult(result);
                    return;
                }

                CalculationStatusRoot status = (CalculationStatusRoot)calculationResponse.Data.Response;
                var calculationId = status.Data.Calculationid;
                Console.WriteLine("Calculation Id: " + calculationId);

                ApiResponse<CalculationStatusRoot> getStatusResponse = null;

                while (status.Data.Status == CalculationStatus.StatusEnum.Queued || status.Data.Status == CalculationStatus.StatusEnum.Executing)
                {
                    if (getStatusResponse != null)
                    {
                        if (getStatusResponse.Headers.ContainsKey("Cache-Control"))
                        {
                            var maxAge = getStatusResponse.Headers["Cache-Control"][0];
                            if (string.IsNullOrWhiteSpace(maxAge))
                            {
                                Console.WriteLine("Sleeping for 2 seconds");
                                // Sleep for at least 2 seconds.
                                Thread.Sleep(2000);
                            }
                            else
                            {
                                var age = int.Parse(maxAge.Replace("max-age=", ""));
                                Console.WriteLine($"Sleeping for {age} seconds");
                                Thread.Sleep(age * 1000);
                            }
                        }
                    }

                    getStatusResponse = calculationApi.GetCalculationStatusByIdWithHttpInfo(calculationId);
                    status = getStatusResponse.Data;
                }
                Console.WriteLine("Calculation Completed");


                foreach (var calculation in status.Data.Units)
                {
                    if (calculation.Value.Status == CalculationUnitStatus.StatusEnum.Success)
                    {
                        var resultResponse = calculationApi.GetCalculationUnitResultByIdWithHttpInfo(calculationId, calculation.Key);
                        PrintResult(resultResponse.Data);
                    }
                    else
                    {
                        Console.WriteLine($"Calculation Unit Id : {calculation.Key} Failed!!!");
                        Console.WriteLine($"Error message : {calculation.Value.Errors?.FirstOrDefault()?.Detail}");
                    }
                }

                Console.ReadKey();
            }
            catch (ApiException e)
            {
                Console.WriteLine($"Status Code: {e.ErrorCode}");
                Console.WriteLine($"Reason : {e.Message}");
                Console.WriteLine(e.StackTrace);
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                throw;
            }
        }
        private static async Task<SDK.PAEngine.Client.Configuration> GetApiConfigurationAsync()
        {
            //Supported authentication methods are below,
            //choose one that satisfies your use case.

            ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
            if (_engineApiConfiguration != null)
            {
                return _engineApiConfiguration;
            }

            #region BasicAuthentication

            /* Basic authentication: FactSetApiKey */
            // See https://github.com/FactSet/enterprise-sdk#api-key
            // for information how to create an API key

            _engineApiConfiguration = new SDK.PAEngine.Client.Configuration
            {
                BasePath = BasePath,
                Username = UserName,
                Password = Password,
            };

            #endregion

            // Uncomment below lines for adding the proxy configuration
            //System.Net.WebProxy webProxy = new System.Net.WebProxy("http://myProxyUrl:80/");
            //webProxy.Credentials = System.Net.CredentialCache.DefaultCredentials;
            //_engineApiConfiguration.Proxy = webProxy;

            #region FactSetOAuth2

            /* (Preferred) OAuth 2.0: FactSetOAuth2 */
            // See https://github.com/FactSet/enterprise-sdk#oauth-20
            // for information on how to create the app-config.json file
            // See https://github.com/FactSet/enterprise-sdk-utils-dotnet#authentication
            // for more information on using the ConfidentialClient class

            //Uncomment below lines and comment the above lines(region:BasicAuthentication) for FactSetOAuth2 Authentication
            //ConfidentialClient confidentialClient = await ConfidentialClient.CreateAsync("");
            //_engineApiConfiguration = new FactSet.SDK.PAEngine.Client.Configuration();
            //_engineApiConfiguration.OAuth2Client = confidentialClient;

            #endregion

            return _engineApiConfiguration;
        }

        private static async Task<PACalculationParametersRoot> GetPaCalculationParametersAsync()
        {
            var componentsApi = new ComponentsApi(await GetApiConfigurationAsync());

            var componentsResponse = componentsApi.GetPAComponents(PADocument);

            var paComponentId = componentsResponse.Data.FirstOrDefault(component => (component.Value.Name == ComponentName && component.Value.Category == ComponentCategory)).Key;
            Console.WriteLine($"PA Component Id : {paComponentId}");

            var pricingSourcesApi = new PricingSourcesApi(await GetApiConfigurationAsync());

            var pricingSourcesResponse = pricingSourcesApi.GetPAPricingSources(PricingSourceName, PricingSourceCategory, PricingSourceDirectory);

            var paPricingSourceId = pricingSourcesResponse.Data.FirstOrDefault(pricingSource => (pricingSource.Value.Name == PricingSourceName && pricingSource.Value.Category == PricingSourceCategory && pricingSource.Value.Directory == PricingSourceDirectory)).Key;
            Console.WriteLine($"PA Pricing Source Id : {paPricingSourceId}");

            var paAccountIdentifier1 = new PAIdentifier(Portfolio1, holdings);
            var PaAccountIdentifier2 = new PAIdentifier(Portfolio2, holdings);
            var paAccounts = new List<PAIdentifier> { paAccountIdentifier1, PaAccountIdentifier2};
            var paBenchmarkIdentifier = new PAIdentifier(Benchmark);
            var paBenchmarks = new List<PAIdentifier> { paBenchmarkIdentifier };
            var paDates1 = new PADateParameters(endDate, frequency1, startDate1);
            var paDates2 = new PADateParameters(endDate, frequency2, startDate2);

            var paPortfolioPricingSources = new List<PACalculationPricingSource> { new PACalculationPricingSource(id: paPricingSourceId) };

            var paDataSources = new PACalculationDataSources(portfoliopricingsources: paPortfolioPricingSources, useportfoliopricingsourcesforbenchmark: true);

            var paCalculation1 = new PACalculationParameters(componentid: paComponentId, accounts: paAccounts, benchmarks: paBenchmarks, datasources: paDataSources, currencyisocode: currency, dates: paDates1);
            var paCalculation2 = new PACalculationParameters(componentid: paComponentId, accounts: paAccounts, benchmarks: paBenchmarks, datasources: paDataSources, currencyisocode: currency, dates: paDates2);


            var calculationParameters = new PACalculationParametersRoot
            {
                Data = new Dictionary<string, PACalculationParameters> { { "1", paCalculation1 }, { "2", paCalculation2 } }
            };

            return calculationParameters;
        }

        private static void PrintResult(ObjectRoot result)
        {
            Console.WriteLine("Calculation Result");

            // converting the data to Package object
            var stachBuilder = StachExtensionFactory.GetRowOrganizedBuilder();
            var stachExtension = stachBuilder.SetPackage(result.Data).Build();
            // To convert package to 2D tables.
            var tables = stachExtension.ConvertToTable();

            Console.WriteLine(tables[0]);
        }
    }
}
Building and running a single unit linked templated component calculation
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using FactSet.Protobuf.Stach.Extensions;
using FactSet.SDK.PAEngine.Api;
using FactSet.SDK.PAEngine.Client;
using FactSet.SDK.PAEngine.Model;
using FactSet.SDK.Utils.Authentication;

namespace FactSet.AnalyticsAPI.PAEngineESDK.Example
{
    public class PAEngineSingleUnitLinkedTemplatedComponentExample
    {
        private static readonly string BasePath = Environment.GetEnvironmentVariable("FACTSET_HOST");
        private static readonly string UserName = Environment.GetEnvironmentVariable("FACTSET_USERNAME");
        private static readonly string Password = Environment.GetEnvironmentVariable("FACTSET_PASSWORD");
        
        private const string ComponentName = "Weights";
        private const string ComponentCategory = "Weights / Exposures";
        private const string ComponentDocument = "PA3_DOCUMENTS:DEFAULT";
        private const string Portfolio = "BENCH:SP50";
        private const string Benchmark = "BENCH:R.1000";

        private const string ColumnName = "Port. Average Weight";
        private const string ColumnCategory = "Portfolio/Position Data";
        private const string Directory = "Factset";
        private const string ColumnStatisticName = "Active Weights";

        private const string GroupCategory = "Fixed Income/Municipals/Class";
        private const string GroupName = "Class - Bloomberg Muni";

        private const string LinkedPATemplateDirectory = "Personal:LinkedPATemplates/";
        private const string LinkedPATemplateDescription = "This is a linked PA template that only returns security level data";
        private const string StartDate = "20180101";
        private const string EndDate = "20181231";
        private const string Frequency = "Monthly";
        private const string CurrencyISOCode = "USD";
        private const string ComponentDetail = "GROUPS";

        private const string TemplatedPAComponentDirectory = "Personal:TemplatedPAComponents/";
        private const string TemplatedPAComponentDescription = "This is a templated PA component";

        private static SDK.PAEngine.Client.Configuration _engineApiConfiguration;

        public static async Task Main(string[] args)
        {
            try
            {
                var calculationParameters = await GetPaCalculationParametersAsync();

                var calculationApi = new PACalculationsApi(await GetApiConfigurationAsync());

                var calculationResponse = calculationApi.PostAndCalculateWithHttpInfo(null, null, calculationParameters);
                // Comment the above line and uncomment the below lines to add cache control configuration. Results are by default cached for 12 hours; Setting max-stale=300 will fetch a cached result which is at max 5 minutes older.
                //var cacheControl = "max-stale=0";
                //var calculationResponse = calculationApi.PostAndCalculateWithHttpInfo(null, cacheControl, calculationParameters);

                if (calculationResponse.StatusCode == HttpStatusCode.Created)
                {
                    ObjectRoot result = (ObjectRoot)calculationResponse.Data.Response;
                    PrintResult(result);
                    return;
                }

                CalculationStatusRoot status = (CalculationStatusRoot)calculationResponse.Data.Response;
                var calculationId = status.Data.Calculationid;
                Console.WriteLine("Calculation Id: " + calculationId);

                ApiResponse<CalculationStatusRoot> getStatusResponse = null;

                while (status.Data.Status == CalculationStatus.StatusEnum.Queued || status.Data.Status == CalculationStatus.StatusEnum.Executing)
                {
                    if (getStatusResponse != null)
                    {
                        if (getStatusResponse.Headers.ContainsKey("Cache-Control"))
                        {
                            var maxAge = getStatusResponse.Headers["Cache-Control"][0];
                            if (string.IsNullOrWhiteSpace(maxAge))
                            {
                                Console.WriteLine("Sleeping for 2 seconds");
                                // Sleep for at least 2 seconds.
                                Thread.Sleep(2000);
                            }
                            else
                            {
                                var age = int.Parse(maxAge.Replace("max-age=", ""));
                                Console.WriteLine($"Sleeping for {age} seconds");
                                Thread.Sleep(age * 1000);
                            }
                        }
                    }

                    getStatusResponse = calculationApi.GetCalculationStatusByIdWithHttpInfo(calculationId);
                    status = getStatusResponse.Data;
                }
                Console.WriteLine("Calculation Completed");


                foreach (var calculation in status.Data.Units)
                {
                    if (calculation.Value.Status == CalculationUnitStatus.StatusEnum.Success)
                    {
                        var resultResponse = calculationApi.GetCalculationUnitResultByIdWithHttpInfo(calculationId, calculation.Key);
                        PrintResult(resultResponse.Data);
                    }
                    else
                    {
                        Console.WriteLine($"Calculation Unit Id : {calculation.Key} Failed!!!");
                        Console.WriteLine($"Error message : {calculation.Value.Errors?.FirstOrDefault()?.Detail}");
                    }
                }

                Console.ReadKey();
            }
            catch (ApiException e)
            {
                Console.WriteLine($"Status Code: {e.ErrorCode}");
                Console.WriteLine($"Reason : {e.Message}");
                Console.WriteLine(e.StackTrace);
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                throw;
            }
        }
        private static async Task<SDK.PAEngine.Client.Configuration> GetApiConfigurationAsync()
        {
            //Supported authentication methods are below,
            //choose one that satisfies your use case.

            ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
            if (_engineApiConfiguration != null)
            {
                return _engineApiConfiguration;
            }

            #region BasicAuthentication

            /* Basic authentication: FactSetApiKey */
            // See https://github.com/FactSet/enterprise-sdk#api-key
            // for information how to create an API key

            _engineApiConfiguration = new SDK.PAEngine.Client.Configuration
            {
                BasePath = BasePath,
                Username = UserName,
                Password = Password,
            };

            #endregion

            // Uncomment below lines for adding the proxy configuration
            //System.Net.WebProxy webProxy = new System.Net.WebProxy("http://myProxyUrl:80/");
            //webProxy.Credentials = System.Net.CredentialCache.DefaultCredentials;
            //_engineApiConfiguration.Proxy = webProxy;

            #region FactSetOAuth2

            /* (Preferred) OAuth 2.0: FactSetOAuth2 */
            // See https://github.com/FactSet/enterprise-sdk#oauth-20
            // for information on how to create the app-config.json file
            // See https://github.com/FactSet/enterprise-sdk-utils-dotnet#authentication
            // for more information on using the ConfidentialClient class

            //Uncomment below lines and comment the above lines(region:BasicAuthentication) for FactSetOAuth2 Authentication
            //ConfidentialClient confidentialClient = await ConfidentialClient.CreateAsync("");
            //_engineApiConfiguration = new FactSet.SDK.PAEngine.Client.Configuration();
            //_engineApiConfiguration.OAuth2Client = confidentialClient;

            #endregion

            return _engineApiConfiguration;
        }

        private static async Task<PACalculationParametersRoot> GetPaCalculationParametersAsync()
        {
            var columnsApi = new ColumnsApi(configuration: await GetApiConfigurationAsync());
            var column = columnsApi.GetPAColumns(name: ColumnName, category: ColumnCategory, directory: Directory);
            var columnId = column.Data.Keys.ToList()[0];

            var columnStatisticsApi = new ColumnStatisticsApi(configuration: await GetApiConfigurationAsync());
            var getAllColumnStatistics = columnStatisticsApi.GetPAColumnStatistics();
            var columnStatisticId = new List<string>()
            {
                getAllColumnStatistics.Data.Keys.FirstOrDefault(id =>
                    (getAllColumnStatistics.Data[id].Name == ColumnStatisticName))
            };

            var columnsIdentifier = new PACalculationColumn(id: columnId, statistics: columnStatisticId);
            var columns = new List<PACalculationColumn> { columnsIdentifier };

            var groupsApi = new GroupsApi(configuration: await GetApiConfigurationAsync());
            var getPAGroups = groupsApi.GetPAGroups();
            var groupId = getPAGroups.Data.Keys.FirstOrDefault(id => (getPAGroups.Data[id].Name == GroupName
                                                                      && getPAGroups.Data[id].Directory == Directory
                                                                      && getPAGroups.Data[id].Category == GroupCategory));

            var groupsIdentifier = new PACalculationGroup(id: groupId);
            var groups = new List<PACalculationGroup> { groupsIdentifier };

            var paAccounts = new List<PAIdentifier> { new PAIdentifier(id: Portfolio) };
            var paBenchmarks = new List<PAIdentifier> { new PAIdentifier(id: Benchmark) };
            var paDates = new PADateParameters(startdate: StartDate, enddate: EndDate, frequency: Frequency);

            var componentsApi = new ComponentsApi(configuration: await GetApiConfigurationAsync());
            var components = componentsApi.GetPAComponents(document: ComponentDocument);
            var parentComponentId = components.Data.FirstOrDefault(component => (component.Value.Name == ComponentName && component.Value.Category == ComponentCategory)).Key;

            var linkedPATemplateParametersRoot = GetLinkedPATemplateParameters(parentComponentId: parentComponentId);
            var linkedPATemplateApi = new LinkedPATemplatesApi(configuration: await GetApiConfigurationAsync());
            var response = linkedPATemplateApi.CreateLinkedPATemplates(linkedPATemplateParametersRoot: linkedPATemplateParametersRoot);

            var parentTemplateId = response.Data.Id;

            var templatedPAComponentParametersRoot =
                GetTemplatedPAComponentParametersRoot(paAccounts: paAccounts, paBenchmarks: paBenchmarks, paDates: paDates, columns: columns, groups: groups, parentTemplateId: parentTemplateId);

            var templatedPAComponentsApi = new TemplatedPAComponentsApi(configuration: await GetApiConfigurationAsync());

            var templatedPAComponentsResponse = templatedPAComponentsApi.CreateTemplatedPAComponents(templatedPAComponentParametersRoot: templatedPAComponentParametersRoot);

            var paComponentId = templatedPAComponentsResponse.Data.Id;

            Console.WriteLine($"PA Component Id : {paComponentId}");

            var paCalculation = new PACalculationParameters(componentid: paComponentId, accounts: paAccounts, benchmarks: paBenchmarks);

            var calculationParameters = new PACalculationParametersRoot
            {
                Data = new Dictionary<string, PACalculationParameters> { { "1", paCalculation } }
            };

            return calculationParameters;
        }

        private static LinkedPATemplateParametersRoot GetLinkedPATemplateParameters(string parentComponentId)
        {
            var mandatory = new List<string>() { "accounts", "benchmarks" };
            var optional = new List<string>() { "groups", "columns", "currencyisocode", "componentdetail", "dates" };
            var linkedPATemplateContent = new TemplateContentTypes(mandatory: mandatory, optional: optional);

            var linkedPATemplateParameters = new LinkedPATemplateParameters(
                directory: LinkedPATemplateDirectory,
                parentComponentId: parentComponentId,
                description: LinkedPATemplateDescription,
                content: linkedPATemplateContent
            );

            var linkedPATemplateParametersRoot = new LinkedPATemplateParametersRoot(data: linkedPATemplateParameters);
            return linkedPATemplateParametersRoot;
        }

        private static TemplatedPAComponentParametersRoot GetTemplatedPAComponentParametersRoot(
            List<PAIdentifier> paAccounts, List<PAIdentifier> paBenchmarks,
            PADateParameters paDates, List<PACalculationColumn> columns, List<PACalculationGroup> groups, string parentTemplateId)
        {
            var templatedPAComponentData = new PAComponentData(
                accounts: paAccounts,
                benchmarks: paBenchmarks,
                groups: groups,
                columns: columns,
                dates: paDates,
                currencyisocode: CurrencyISOCode,
                componentdetail: ComponentDetail
            );

            var templatedPAComponentParameters = new TemplatedPAComponentParameters(
                directory: TemplatedPAComponentDirectory,
                parentTemplateId: parentTemplateId,
                description: TemplatedPAComponentDescription,
                componentData: templatedPAComponentData
            );

            var templatedPAComponentParametersRoot =
                new TemplatedPAComponentParametersRoot(data: templatedPAComponentParameters);

            return templatedPAComponentParametersRoot;
        }

        private static void PrintResult(ObjectRoot result)
        {
            Console.WriteLine("Calculation Result");

            // converting the data to Package object
            var stachBuilder = StachExtensionFactory.GetRowOrganizedBuilder();
            var stachExtension = stachBuilder.SetPackage(result.Data).Build();
            // To convert package to 2D tables.
            var tables = stachExtension.ConvertToTable();

            Console.WriteLine(tables[0]);
        }
    }
}
Building and running a single unit unlinked templated component calculation
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using FactSet.Protobuf.Stach.Extensions;
using FactSet.SDK.PAEngine.Api;
using FactSet.SDK.PAEngine.Client;
using FactSet.SDK.PAEngine.Model;
using FactSet.SDK.Utils.Authentication;

namespace FactSet.AnalyticsAPI.PAEngineESDK.Example
{
    public class PAEngineSingleUnitUnlinkedTemplatedComponentExample
    {
        private static readonly string BasePath = Environment.GetEnvironmentVariable("FACTSET_HOST");
        private static readonly string UserName = Environment.GetEnvironmentVariable("FACTSET_USERNAME");
        private static readonly string Password = Environment.GetEnvironmentVariable("FACTSET_PASSWORD");
        private static readonly string ProxyUrl = Environment.GetEnvironmentVariable("PROXY_URL");

        private const string Portfolio = "BENCH:SP50";
        private const string Benchmark = "BENCH:R.1000";

        private const string ColumnName = "Port. Average Weight";
        private const string ColumnCategory = "Portfolio/Position Data";
        private const string Directory = "Factset";
        private const string ColumnStatisticName = "Active Weights";

        private const string GroupCategory = "Fixed Income/Municipals/Class";
        private const string GroupName = "Class - Bloomberg Muni";

        private const string UnlinkedPATemplateDirectory = "Personal:UnlinkedPATemplates";
        private const string UnlinkedPATemplateTypeId = "996E90B981AEE83F14029ED3D309FB3F03EC6E2ACC7FD42C22CBD5D279502CFD";
        private const string UnlinkedPATemplateDescription = "This is an unlinked PA template that only returns security level data";
        private const string StartDate = "20180101";
        private const string EndDate = "20181231";
        private const string Frequency = "Monthly";
        private const string CurrencyISOCode = "USD";
        private const string ComponentDetail = "GROUPS";

        private const string TemplatedPAComponentDirectory = "Personal:TemplatedPAComponents/";
        private const string TemplatedPAComponentDescription = "This is a templated PA component";

        private static SDK.PAEngine.Client.Configuration _engineApiConfiguration;

        public static async Task Main(string[] args)
        {
            try
            {
                var calculationParameters = await GetPaCalculationParametersAsync();

                var calculationApi = new PACalculationsApi(await GetApiConfigurationAsync());

                var calculationResponse = calculationApi.PostAndCalculateWithHttpInfo(null, null, calculationParameters);
                // Comment the above line and uncomment the below lines to add cache control configuration. Results are by default cached for 12 hours; Setting max-stale=300 will fetch a cached result which is at max 5 minutes older.
                //var cacheControl = "max-stale=0";
                //var calculationResponse = calculationApi.PostAndCalculateWithHttpInfo(null, cacheControl, calculationParameters);

                if (calculationResponse.StatusCode == HttpStatusCode.Created)
                {
                    ObjectRoot result = (ObjectRoot)calculationResponse.Data.Response;
                    PrintResult(result);
                    return;
                }

                CalculationStatusRoot status = (CalculationStatusRoot)calculationResponse.Data.Response;
                var calculationId = status.Data.Calculationid;
                Console.WriteLine("Calculation Id: " + calculationId);

                ApiResponse<CalculationStatusRoot> getStatusResponse = null;

                while (status.Data.Status == CalculationStatus.StatusEnum.Queued || status.Data.Status == CalculationStatus.StatusEnum.Executing)
                {
                    if (getStatusResponse != null)
                    {
                        if (getStatusResponse.Headers.ContainsKey("Cache-Control"))
                        {
                            var maxAge = getStatusResponse.Headers["Cache-Control"][0];
                            if (string.IsNullOrWhiteSpace(maxAge))
                            {
                                Console.WriteLine("Sleeping for 2 seconds");
                                // Sleep for at least 2 seconds.
                                Thread.Sleep(2000);
                            }
                            else
                            {
                                var age = int.Parse(maxAge.Replace("max-age=", ""));
                                Console.WriteLine($"Sleeping for {age} seconds");
                                Thread.Sleep(age * 1000);
                            }
                        }
                    }

                    getStatusResponse = calculationApi.GetCalculationStatusByIdWithHttpInfo(calculationId);
                    status = getStatusResponse.Data;
                }
                Console.WriteLine("Calculation Completed");


                foreach (var calculation in status.Data.Units)
                {
                    if (calculation.Value.Status == CalculationUnitStatus.StatusEnum.Success)
                    {
                        var resultResponse = calculationApi.GetCalculationUnitResultByIdWithHttpInfo(calculationId, calculation.Key);
                        PrintResult(resultResponse.Data);
                    }
                    else
                    {
                        Console.WriteLine($"Calculation Unit Id : {calculation.Key} Failed!!!");
                        Console.WriteLine($"Error message : {calculation.Value.Errors?.FirstOrDefault()?.Detail}");
                    }
                }

                Console.ReadKey();
            }
            catch (ApiException e)
            {
                Console.WriteLine($"Status Code: {e.ErrorCode}");
                Console.WriteLine($"Reason : {e.Message}");
                Console.WriteLine(e.StackTrace);
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                throw;
            }
        }
        private static async Task<SDK.PAEngine.Client.Configuration> GetApiConfigurationAsync()
        {
            //Supported authentication methods are below,
            //choose one that satisfies your use case.

            ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
            if (_engineApiConfiguration != null)
            {
                return _engineApiConfiguration;
            }

            #region BasicAuthentication

            /* Basic authentication: FactSetApiKey */
            // See https://github.com/FactSet/enterprise-sdk#api-key
            // for information how to create an API key

            _engineApiConfiguration = new SDK.PAEngine.Client.Configuration
            {
                BasePath = BasePath,
                Username = UserName,
                Password = Password,
            };

            #endregion

            // Uncomment below lines for adding the proxy configuration
            //System.Net.WebProxy webProxy = new System.Net.WebProxy("http://myProxyUrl:80/");
            //webProxy.Credentials = System.Net.CredentialCache.DefaultCredentials;
            //_engineApiConfiguration.Proxy = webProxy;

            #region FactSetOAuth2

            /* (Preferred) OAuth 2.0: FactSetOAuth2 */
            // See https://github.com/FactSet/enterprise-sdk#oauth-20
            // for information on how to create the app-config.json file
            // See https://github.com/FactSet/enterprise-sdk-utils-dotnet#authentication
            // for more information on using the ConfidentialClient class

            //Uncomment below lines and comment the above lines(region:BasicAuthentication) for FactSetOAuth2 Authentication
            //ConfidentialClient confidentialClient = await ConfidentialClient.CreateAsync("");
            //_engineApiConfiguration = new FactSet.SDK.PAEngine.Client.Configuration();
            //_engineApiConfiguration.OAuth2Client = confidentialClient;

            #endregion

            return _engineApiConfiguration;
        }

        private static async Task<PACalculationParametersRoot> GetPaCalculationParametersAsync()
        {
            var columnsApi = new ColumnsApi(configuration: await GetApiConfigurationAsync());
            var column = columnsApi.GetPAColumns(name: ColumnName, category: ColumnCategory, directory: Directory);
            var columnId = column.Data.Keys.ToList()[0];

            var columnStatisticsApi = new ColumnStatisticsApi(configuration: await GetApiConfigurationAsync());
            var getAllColumnStatistics = columnStatisticsApi.GetPAColumnStatistics();
            var columnStatisticId = new List<string>()
            {
                getAllColumnStatistics.Data.Keys.FirstOrDefault(id =>
                    (getAllColumnStatistics.Data[id].Name == ColumnStatisticName))
            };

            var columnsIdentifier = new PACalculationColumn(columnId, columnStatisticId);
            var columns = new List<PACalculationColumn> { columnsIdentifier };

            var groupsApi = new GroupsApi(configuration: await GetApiConfigurationAsync());
            var getPAGroups = groupsApi.GetPAGroups();
            var groupId = getPAGroups.Data.Keys.FirstOrDefault(id => (getPAGroups.Data[id].Name == GroupName
                                                                      && getPAGroups.Data[id].Directory == Directory
                                                                      && getPAGroups.Data[id].Category == GroupCategory));


            var groupsIdentifier = new PACalculationGroup(id: groupId);
            var groups = new List<PACalculationGroup> { groupsIdentifier };

            var paAccounts = new List<PAIdentifier> { new PAIdentifier(id: Portfolio) };
            var paBenchmarks = new List<PAIdentifier> { new PAIdentifier(id: Benchmark) };
            var paDates = new PADateParameters(startdate: StartDate, enddate: EndDate, frequency: Frequency);


            var unlinkedPATemplateParametersRoot = GetUnlinkedPATemplateParameters(paAccounts: paAccounts, paBenchmarks: paBenchmarks, paDates: paDates, columns: columns, groups: groups);
            var unlinkedPATemplateApi = new UnlinkedPATemplatesApi(configuration: await GetApiConfigurationAsync());
            var response = unlinkedPATemplateApi.CreateUnlinkedPATemplates(unlinkedPATemplateParametersRoot: unlinkedPATemplateParametersRoot);

            var parentTemplateId = response.Data.Id;

            var templatedPAComponentParametersRoot =
                GetTemplatedPAComponentParametersRoot(paAccounts: paAccounts, paBenchmarks: paBenchmarks, paDates: paDates, columns: columns, groups: groups, parentTemplateId: parentTemplateId);

            var templatedPAComponentsApi = new TemplatedPAComponentsApi(configuration: await GetApiConfigurationAsync());

            var templatedPAComponentsResponse = templatedPAComponentsApi.CreateTemplatedPAComponents(templatedPAComponentParametersRoot: templatedPAComponentParametersRoot);

            var paComponentId = templatedPAComponentsResponse.Data.Id;

            Console.WriteLine($"PA Component Id : {paComponentId}");

            var paCalculation = new PACalculationParameters(componentid: paComponentId, accounts: paAccounts, benchmarks: paBenchmarks);

            var calculationParameters = new PACalculationParametersRoot
            {
                Data = new Dictionary<string, PACalculationParameters> { { "1", paCalculation } }
            };

            return calculationParameters;
        }

        private static UnlinkedPATemplateParametersRoot GetUnlinkedPATemplateParameters(List<PAIdentifier> paAccounts, List<PAIdentifier> paBenchmarks,
            PADateParameters paDates, List<PACalculationColumn> columns, List<PACalculationGroup> groups)
        {
            var mandatory = new List<string>() { "accounts", "benchmarks" };
            var optional = new List<string>() { "groups", "columns", "currencyisocode", "componentdetail", "dates" };
            var unlinkedPATemplateContent = new TemplateContentTypes(mandatory: mandatory, optional: optional);

            var unlinkedPATemplateParameters = new UnlinkedPATemplateParameters(
                directory: UnlinkedPATemplateDirectory,
                templateTypeId: UnlinkedPATemplateTypeId,
                description: UnlinkedPATemplateDescription,
                accounts: paAccounts,
                benchmarks: paBenchmarks,
                columns: columns,
                dates: paDates,
                groups: groups,
                currencyisocode: CurrencyISOCode,
                componentdetail: ComponentDetail,
                content: unlinkedPATemplateContent
            );

            var unlinkedPATemplateParametersRoot = new UnlinkedPATemplateParametersRoot(data: unlinkedPATemplateParameters);
            return unlinkedPATemplateParametersRoot;
        }

        private static TemplatedPAComponentParametersRoot GetTemplatedPAComponentParametersRoot(
            List<PAIdentifier> paAccounts, List<PAIdentifier> paBenchmarks,
            PADateParameters paDates, List<PACalculationColumn> columns, List<PACalculationGroup> groups, string parentTemplateId)
        {
            var templatedPAComponentData = new PAComponentData(
                accounts: paAccounts,
                benchmarks: paBenchmarks,
                groups: groups,
                columns: columns,
                dates: paDates,
                currencyisocode: CurrencyISOCode,
                componentdetail: ComponentDetail
            );

            var templatedPAComponentParameters = new TemplatedPAComponentParameters(
                directory: TemplatedPAComponentDirectory,
                parentTemplateId: parentTemplateId,
                description: TemplatedPAComponentDescription,
                componentData: templatedPAComponentData
            );

            var templatedPAComponentParametersRoot =
                new TemplatedPAComponentParametersRoot(data: templatedPAComponentParameters);

            return templatedPAComponentParametersRoot;
        }

        private static void PrintResult(ObjectRoot result)
        {
            Console.WriteLine("Calculation Result");

            // converting the data to Package object
            var stachBuilder = StachExtensionFactory.GetRowOrganizedBuilder();
            var stachExtension = stachBuilder.SetPackage(result.Data).Build();
            // To convert package to 2D tables.
            var tables = stachExtension.ConvertToTable();

            Console.WriteLine(tables[0]);
        }
    }
}
Building and running multiple unit linked templated component calculations
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using FactSet.Protobuf.Stach.Extensions;
using FactSet.SDK.PAEngine.Api;
using FactSet.SDK.PAEngine.Client;
using FactSet.SDK.PAEngine.Model;
using FactSet.SDK.Utils.Authentication;

namespace FactSet.AnalyticsAPI.PAEngineESDK.Example
{
    public class PAEngineMultipleUnitLinkedTemplatedComponentExample
    {
        private static readonly string BasePath = Environment.GetEnvironmentVariable("FACTSET_HOST");
        private static readonly string UserName = Environment.GetEnvironmentVariable("FACTSET_USERNAME");
        private static readonly string Password = Environment.GetEnvironmentVariable("FACTSET_PASSWORD");
        
        private const string ComponentName = "Weights";
        private const string ComponentCategory = "Weights / Exposures";
        private const string ComponentDocument = "PA3_DOCUMENTS:DEFAULT";
        private const string Portfolio = "BENCH:SP50";
        private const string Benchmark = "BENCH:R.1000";

        private const string ColumnName = "Port. Average Weight";
        private const string ColumnCategory = "Portfolio/Position Data";
        private const string Directory = "Factset";
        private const string ColumnStatisticName = "Active Weights";

        private const string GroupCategory = "Fixed Income/Municipals/Class";
        private const string GroupName = "Class - Bloomberg Muni";

        private const string LinkedPATemplateDirectory = "Personal:LinkedPATemplates/";
        private const string LinkedPATemplateDescription = "This is a linked PA template that only returns security level data";
        private const string StartDate = "20180101";
        private const string EndDate = "20181231";
        private const string Frequency = "Monthly";
        private const string CurrencyISOCode = "USD";
        private const string ComponentDetail = "GROUPS";

        private const string TemplatedPAComponentDirectory = "Personal:TemplatedPAComponents/";
        private const string TemplatedPAComponentDescription = "This is a templated PA component";

        private static SDK.PAEngine.Client.Configuration _engineApiConfiguration;

        public static async Task Main(string[] args)
        {
            try
            {
                var calculationParameters = await GetPaCalculationParametersAsync();

                var calculationApi = new PACalculationsApi(await GetApiConfigurationAsync());

                var calculationResponse = calculationApi.PostAndCalculateWithHttpInfo(null, null, calculationParameters);
                // Comment the above line and uncomment the below lines to add cache control configuration. Results are by default cached for 12 hours; Setting max-stale=300 will fetch a cached result which is at max 5 minutes older.
                //var cacheControl = "max-stale=0";
                //var calculationResponse = calculationApi.PostAndCalculateWithHttpInfo(null, cacheControl, calculationParameters);

                if (calculationResponse.StatusCode == HttpStatusCode.Created)
                {
                    ObjectRoot result = (ObjectRoot)calculationResponse.Data.Response;
                    PrintResult(result);
                    return;
                }

                CalculationStatusRoot status = (CalculationStatusRoot)calculationResponse.Data.Response;
                var calculationId = status.Data.Calculationid;
                Console.WriteLine("Calculation Id: " + calculationId);

                ApiResponse<CalculationStatusRoot> getStatusResponse = null;

                while (status.Data.Status == CalculationStatus.StatusEnum.Queued || status.Data.Status == CalculationStatus.StatusEnum.Executing)
                {
                    if (getStatusResponse != null)
                    {
                        if (getStatusResponse.Headers.ContainsKey("Cache-Control"))
                        {
                            var maxAge = getStatusResponse.Headers["Cache-Control"][0];
                            if (string.IsNullOrWhiteSpace(maxAge))
                            {
                                Console.WriteLine("Sleeping for 2 seconds");
                                // Sleep for at least 2 seconds.
                                Thread.Sleep(2000);
                            }
                            else
                            {
                                var age = int.Parse(maxAge.Replace("max-age=", ""));
                                Console.WriteLine($"Sleeping for {age} seconds");
                                Thread.Sleep(age * 1000);
                            }
                        }
                    }

                    getStatusResponse = calculationApi.GetCalculationStatusByIdWithHttpInfo(calculationId);
                    status = getStatusResponse.Data;
                }
                Console.WriteLine("Calculation Completed");


                foreach (var calculation in status.Data.Units)
                {
                    if (calculation.Value.Status == CalculationUnitStatus.StatusEnum.Success)
                    {
                        var resultResponse = calculationApi.GetCalculationUnitResultByIdWithHttpInfo(calculationId, calculation.Key);
                        PrintResult(resultResponse.Data);
                    }
                    else
                    {
                        Console.WriteLine($"Calculation Unit Id : {calculation.Key} Failed!!!");
                        Console.WriteLine($"Error message : {calculation.Value.Errors?.FirstOrDefault()?.Detail}");
                    }
                }

                Console.ReadKey();
            }
            catch (ApiException e)
            {
                Console.WriteLine($"Status Code: {e.ErrorCode}");
                Console.WriteLine($"Reason : {e.Message}");
                Console.WriteLine(e.StackTrace);
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                throw;
            }
        }
        private static async Task<SDK.PAEngine.Client.Configuration> GetApiConfigurationAsync()
        {
            //Supported authentication methods are below,
            //choose one that satisfies your use case.

            ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
            if (_engineApiConfiguration != null)
            {
                return _engineApiConfiguration;
            }

            #region BasicAuthentication

            /* Basic authentication: FactSetApiKey */
            // See https://github.com/FactSet/enterprise-sdk#api-key
            // for information how to create an API key

            _engineApiConfiguration = new SDK.PAEngine.Client.Configuration
            {
                BasePath = BasePath,
                Username = UserName,
                Password = Password,
            };

            #endregion

            // Uncomment below lines for adding the proxy configuration
            //System.Net.WebProxy webProxy = new System.Net.WebProxy("http://myProxyUrl:80/");
            //webProxy.Credentials = System.Net.CredentialCache.DefaultCredentials;
            //_engineApiConfiguration.Proxy = webProxy;

            #region FactSetOAuth2

            /* (Preferred) OAuth 2.0: FactSetOAuth2 */
            // See https://github.com/FactSet/enterprise-sdk#oauth-20
            // for information on how to create the app-config.json file
            // See https://github.com/FactSet/enterprise-sdk-utils-dotnet#authentication
            // for more information on using the ConfidentialClient class

            //Uncomment below lines and comment the above lines(region:BasicAuthentication) for FactSetOAuth2 Authentication
            //ConfidentialClient confidentialClient = await ConfidentialClient.CreateAsync("");
            //_engineApiConfiguration = new FactSet.SDK.PAEngine.Client.Configuration();
            //_engineApiConfiguration.OAuth2Client = confidentialClient;
            
            #endregion

            return _engineApiConfiguration;
        }

        private static async Task<PACalculationParametersRoot> GetPaCalculationParametersAsync()
        {
            var columnsApi = new ColumnsApi(configuration: await GetApiConfigurationAsync());
            var column = columnsApi.GetPAColumns(name: ColumnName, category: ColumnCategory, directory: Directory);
            var columnId = column.Data.Keys.ToList()[0];

            var columnStatisticsApi = new ColumnStatisticsApi(configuration: await GetApiConfigurationAsync());
            var getAllColumnStatistics = columnStatisticsApi.GetPAColumnStatistics();
            var columnStatisticId = new List<string>()
            {
                getAllColumnStatistics.Data.Keys.FirstOrDefault(id =>
                    (getAllColumnStatistics.Data[id].Name == ColumnStatisticName))
            };

            var columnsIdentifier = new PACalculationColumn(id: columnId, statistics: columnStatisticId);
            var columns = new List<PACalculationColumn> { columnsIdentifier };

            var groupsApi = new GroupsApi(configuration: await GetApiConfigurationAsync());
            var getPAGroups = groupsApi.GetPAGroups();
            var groupId = getPAGroups.Data.Keys.FirstOrDefault(id => (getPAGroups.Data[id].Name == GroupName
                                                                      && getPAGroups.Data[id].Directory == Directory
                                                                      && getPAGroups.Data[id].Category == GroupCategory));

            var groupsIdentifier = new PACalculationGroup(id: groupId);
            var groups = new List<PACalculationGroup> { groupsIdentifier };

            var paAccounts = new List<PAIdentifier> { new PAIdentifier(id: Portfolio) };
            var paBenchmarks = new List<PAIdentifier> { new PAIdentifier(id: Benchmark) };
            var paDates = new PADateParameters(startdate: StartDate, enddate: EndDate, frequency: Frequency);

            var componentsApi = new ComponentsApi(configuration: await GetApiConfigurationAsync());
            var components = componentsApi.GetPAComponents(document: ComponentDocument);
            var parentComponentId = components.Data.FirstOrDefault(component => (component.Value.Name == ComponentName && component.Value.Category == ComponentCategory)).Key;

            var linkedPATemplateParametersRoot = GetLinkedPATemplateParameters(parentComponentId: parentComponentId);
            var linkedPATemplateApi = new LinkedPATemplatesApi(configuration: await GetApiConfigurationAsync());
            var response = linkedPATemplateApi.CreateLinkedPATemplates(linkedPATemplateParametersRoot: linkedPATemplateParametersRoot);

            var parentTemplateId = response.Data.Id;

            var templatedPAComponentParametersRoot =
                GetTemplatedPAComponentParametersRoot(paAccounts: paAccounts, paBenchmarks: paBenchmarks, paDates: paDates, columns: columns, groups: groups, parentTemplateId: parentTemplateId);

            var templatedPAComponentsApi = new TemplatedPAComponentsApi(configuration: await GetApiConfigurationAsync());

            var templatedPAComponentsResponse = templatedPAComponentsApi.CreateTemplatedPAComponents(templatedPAComponentParametersRoot: templatedPAComponentParametersRoot);

            var paComponentId = templatedPAComponentsResponse.Data.Id;

            Console.WriteLine($"PA Component Id : {paComponentId}");

            var paCalculation = new PACalculationParameters(componentid: paComponentId, accounts: paAccounts, benchmarks: paBenchmarks);

            var calculationParameters = new PACalculationParametersRoot
            {
                Data = new Dictionary<string, PACalculationParameters> { { "1", paCalculation } , { "2", paCalculation } }
            };

            return calculationParameters;
        }

        private static LinkedPATemplateParametersRoot GetLinkedPATemplateParameters(string parentComponentId)
        {
            var mandatory = new List<string>() { "accounts", "benchmarks" };
            var optional = new List<string>() { "groups", "columns", "currencyisocode", "componentdetail", "dates" };
            var linkedPATemplateContent = new TemplateContentTypes(mandatory: mandatory, optional: optional);

            var linkedPATemplateParameters = new LinkedPATemplateParameters(
                directory: LinkedPATemplateDirectory,
                parentComponentId: parentComponentId,
                description: LinkedPATemplateDescription,
                content: linkedPATemplateContent
            );

            var linkedPATemplateParametersRoot = new LinkedPATemplateParametersRoot(data: linkedPATemplateParameters);
            return linkedPATemplateParametersRoot;
        }

        private static TemplatedPAComponentParametersRoot GetTemplatedPAComponentParametersRoot(
            List<PAIdentifier> paAccounts, List<PAIdentifier> paBenchmarks,
            PADateParameters paDates, List<PACalculationColumn> columns, List<PACalculationGroup> groups, string parentTemplateId)
        {
            var templatedPAComponentData = new PAComponentData(
                accounts: paAccounts,
                benchmarks: paBenchmarks,
                groups: groups,
                columns: columns,
                dates: paDates,
                currencyisocode: CurrencyISOCode,
                componentdetail: ComponentDetail
            );

            var templatedPAComponentParameters = new TemplatedPAComponentParameters(
                directory: TemplatedPAComponentDirectory,
                parentTemplateId: parentTemplateId,
                description: TemplatedPAComponentDescription,
                componentData: templatedPAComponentData
            );

            var templatedPAComponentParametersRoot =
                new TemplatedPAComponentParametersRoot(data: templatedPAComponentParameters);

            return templatedPAComponentParametersRoot;
        }

        private static void PrintResult(ObjectRoot result)
        {
            Console.WriteLine("Calculation Result");

            // converting the data to Package object
            var stachBuilder = StachExtensionFactory.GetRowOrganizedBuilder();
            var stachExtension = stachBuilder.SetPackage(result.Data).Build();
            // To convert package to 2D tables.
            var tables = stachExtension.ConvertToTable();

            Console.WriteLine(tables[0]);
        }
    }
}
Building and running multiple unit unlinked templated component calculations
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using FactSet.Protobuf.Stach.Extensions;
using FactSet.SDK.PAEngine.Api;
using FactSet.SDK.PAEngine.Client;
using FactSet.SDK.PAEngine.Model;
using FactSet.SDK.Utils.Authentication;

namespace FactSet.AnalyticsAPI.PAEngineESDK.Example
{
    public class PAEngineMultipleUnitUnlinkedTemplatedComponentExample
    {
        private static readonly string BasePath = Environment.GetEnvironmentVariable("FACTSET_HOST");
        private static readonly string UserName = Environment.GetEnvironmentVariable("FACTSET_USERNAME");
        private static readonly string Password = Environment.GetEnvironmentVariable("FACTSET_PASSWORD");
        
        private const string Portfolio = "BENCH:SP50";
        private const string Benchmark = "BENCH:R.1000";

        private const string ColumnName = "Port. Average Weight";
        private const string ColumnCategory = "Portfolio/Position Data";
        private const string Directory = "Factset";
        private const string ColumnStatisticName = "Active Weights";

        private const string GroupCategory = "Fixed Income/Municipals/Class";
        private const string GroupName = "Class - Bloomberg Muni";

        private const string UnlinkedPATemplateDirectory = "Personal:UnlinkedPATemplates";
        private const string UnlinkedPATemplateTypeId = "996E90B981AEE83F14029ED3D309FB3F03EC6E2ACC7FD42C22CBD5D279502CFD";
        private const string UnlinkedPATemplateDescription = "This is an unlinked PA template that only returns security level data";
        private const string StartDate = "20180101";
        private const string EndDate = "20181231";
        private const string Frequency = "Monthly";
        private const string CurrencyISOCode = "USD";
        private const string ComponentDetail = "GROUPS";

        private const string TemplatedPAComponentDirectory = "Personal:TemplatedPAComponents/";
        private const string TemplatedPAComponentDescription = "This is a templated PA component";

        private static SDK.PAEngine.Client.Configuration _engineApiConfiguration;

        public static async Task Main(string[] args)
        {
            try
            {
                var calculationParameters = await GetPaCalculationParametersAsync();

                var calculationApi = new PACalculationsApi(await GetApiConfigurationAsync());

                var calculationResponse = calculationApi.PostAndCalculateWithHttpInfo(null, null, calculationParameters);
                // Comment the above line and uncomment the below lines to add cache control configuration. Results are by default cached for 12 hours; Setting max-stale=300 will fetch a cached result which is at max 5 minutes older.
                //var cacheControl = "max-stale=0";
                //var calculationResponse = calculationApi.PostAndCalculateWithHttpInfo(null, cacheControl, calculationParameters);

                if (calculationResponse.StatusCode == HttpStatusCode.Created)
                {
                    ObjectRoot result = (ObjectRoot)calculationResponse.Data.Response;
                    PrintResult(result);
                    return;
                }

                CalculationStatusRoot status = (CalculationStatusRoot)calculationResponse.Data.Response;
                var calculationId = status.Data.Calculationid;
                Console.WriteLine("Calculation Id: " + calculationId);

                ApiResponse<CalculationStatusRoot> getStatusResponse = null;

                while (status.Data.Status == CalculationStatus.StatusEnum.Queued || status.Data.Status == CalculationStatus.StatusEnum.Executing)
                {
                    if (getStatusResponse != null)
                    {
                        if (getStatusResponse.Headers.ContainsKey("Cache-Control"))
                        {
                            var maxAge = getStatusResponse.Headers["Cache-Control"][0];
                            if (string.IsNullOrWhiteSpace(maxAge))
                            {
                                Console.WriteLine("Sleeping for 2 seconds");
                                // Sleep for at least 2 seconds.
                                Thread.Sleep(2000);
                            }
                            else
                            {
                                var age = int.Parse(maxAge.Replace("max-age=", ""));
                                Console.WriteLine($"Sleeping for {age} seconds");
                                Thread.Sleep(age * 1000);
                            }
                        }
                    }

                    getStatusResponse = calculationApi.GetCalculationStatusByIdWithHttpInfo(calculationId);
                    status = getStatusResponse.Data;
                }
                Console.WriteLine("Calculation Completed");


                foreach (var calculation in status.Data.Units)
                {
                    if (calculation.Value.Status == CalculationUnitStatus.StatusEnum.Success)
                    {
                        var resultResponse = calculationApi.GetCalculationUnitResultByIdWithHttpInfo(calculationId, calculation.Key);
                        PrintResult(resultResponse.Data);
                    }
                    else
                    {
                        Console.WriteLine($"Calculation Unit Id : {calculation.Key} Failed!!!");
                        Console.WriteLine($"Error message : {calculation.Value.Errors?.FirstOrDefault()?.Detail}");
                    }
                }

                Console.ReadKey();
            }
            catch (ApiException e)
            {
                Console.WriteLine($"Status Code: {e.ErrorCode}");
                Console.WriteLine($"Reason : {e.Message}");
                Console.WriteLine(e.StackTrace);
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                throw;
            }
        }
        private static async Task<SDK.PAEngine.Client.Configuration> GetApiConfigurationAsync()
        {
            //Supported authentication methods are below,
            //choose one that satisfies your use case.

            ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
            if (_engineApiConfiguration != null)
            {
                return _engineApiConfiguration;
            }

            #region BasicAuthentication

            /* Basic authentication: FactSetApiKey */
            // See https://github.com/FactSet/enterprise-sdk#api-key
            // for information how to create an API key

            _engineApiConfiguration = new SDK.PAEngine.Client.Configuration
            {
                BasePath = BasePath,
                Username = UserName,
                Password = Password,
            };

            #endregion

            // Uncomment below lines for adding the proxy configuration
            //System.Net.WebProxy webProxy = new System.Net.WebProxy("http://myProxyUrl:80/");
            //webProxy.Credentials = System.Net.CredentialCache.DefaultCredentials;
            //_engineApiConfiguration.Proxy = webProxy;

            #region FactSetOAuth2

            /* (Preferred) OAuth 2.0: FactSetOAuth2 */
            // See https://github.com/FactSet/enterprise-sdk#oauth-20
            // for information on how to create the app-config.json file
            // See https://github.com/FactSet/enterprise-sdk-utils-dotnet#authentication
            // for more information on using the ConfidentialClient class

            //Uncomment below lines and comment the above lines(region:BasicAuthentication) for FactSetOAuth2 Authentication
            //ConfidentialClient confidentialClient = await ConfidentialClient.CreateAsync("");
            //_engineApiConfiguration = new FactSet.SDK.PAEngine.Client.Configuration();
            //_engineApiConfiguration.OAuth2Client = confidentialClient;

            #endregion

            return _engineApiConfiguration;
        }

        private static async Task<PACalculationParametersRoot> GetPaCalculationParametersAsync()
        {
            var columnsApi = new ColumnsApi(configuration: await GetApiConfigurationAsync());
            var column = columnsApi.GetPAColumns(name: ColumnName, category: ColumnCategory, directory: Directory);
            var columnId = column.Data.Keys.ToList()[0];

            var columnStatisticsApi = new ColumnStatisticsApi(configuration: await GetApiConfigurationAsync());
            var getAllColumnStatistics = columnStatisticsApi.GetPAColumnStatistics();
            var columnStatisticId = new List<string>()
            {
                getAllColumnStatistics.Data.Keys.FirstOrDefault(id =>
                    (getAllColumnStatistics.Data[id].Name == ColumnStatisticName))
            };

            var columnsIdentifier = new PACalculationColumn(columnId, columnStatisticId);
            var columns = new List<PACalculationColumn> { columnsIdentifier };

            var groupsApi = new GroupsApi(configuration: await GetApiConfigurationAsync());
            var getPAGroups = groupsApi.GetPAGroups();
            var groupId = getPAGroups.Data.Keys.FirstOrDefault(id => (getPAGroups.Data[id].Name == GroupName
                                                                      && getPAGroups.Data[id].Directory == Directory
                                                                      && getPAGroups.Data[id].Category == GroupCategory));


            var groupsIdentifier = new PACalculationGroup(id: groupId);
            var groups = new List<PACalculationGroup> { groupsIdentifier };

            var paAccounts = new List<PAIdentifier> { new PAIdentifier(id: Portfolio) };
            var paBenchmarks = new List<PAIdentifier> { new PAIdentifier(id: Benchmark) };
            var paDates = new PADateParameters(startdate: StartDate, enddate: EndDate, frequency: Frequency);


            var unlinkedPATemplateParametersRoot = GetUnlinkedPATemplateParameters(paAccounts: paAccounts, paBenchmarks: paBenchmarks, paDates: paDates, columns: columns, groups: groups);
            var unlinkedPATemplateApi = new UnlinkedPATemplatesApi(configuration: await GetApiConfigurationAsync());
            var response = unlinkedPATemplateApi.CreateUnlinkedPATemplates(unlinkedPATemplateParametersRoot: unlinkedPATemplateParametersRoot);

            var parentTemplateId = response.Data.Id;

            var templatedPAComponentParametersRoot =
                GetTemplatedPAComponentParametersRoot(paAccounts: paAccounts, paBenchmarks: paBenchmarks, paDates: paDates, columns: columns, groups: groups, parentTemplateId: parentTemplateId);

            var templatedPAComponentsApi = new TemplatedPAComponentsApi(configuration: await GetApiConfigurationAsync());

            var templatedPAComponentsResponse = templatedPAComponentsApi.CreateTemplatedPAComponents(templatedPAComponentParametersRoot: templatedPAComponentParametersRoot);

            var paComponentId = templatedPAComponentsResponse.Data.Id;

            Console.WriteLine($"PA Component Id : {paComponentId}");

            var paCalculation = new PACalculationParameters(componentid: paComponentId, accounts: paAccounts, benchmarks: paBenchmarks);

            var calculationParameters = new PACalculationParametersRoot
            {
                Data = new Dictionary<string, PACalculationParameters> { { "1", paCalculation }, { "2", paCalculation } }
            };

            return calculationParameters;
        }

        private static UnlinkedPATemplateParametersRoot GetUnlinkedPATemplateParameters(List<PAIdentifier> paAccounts, List<PAIdentifier> paBenchmarks,
            PADateParameters paDates, List<PACalculationColumn> columns, List<PACalculationGroup> groups)
        {
            var mandatory = new List<string>() { "accounts", "benchmarks" };
            var optional = new List<string>() { "groups", "columns", "currencyisocode", "componentdetail", "dates" };
            var unlinkedPATemplateContent = new TemplateContentTypes(mandatory: mandatory, optional: optional);

            var unlinkedPATemplateParameters = new UnlinkedPATemplateParameters(
                directory: UnlinkedPATemplateDirectory,
                templateTypeId: UnlinkedPATemplateTypeId,
                description: UnlinkedPATemplateDescription,
                accounts: paAccounts,
                benchmarks: paBenchmarks,
                columns: columns,
                dates: paDates,
                groups: groups,
                currencyisocode: CurrencyISOCode,
                componentdetail: ComponentDetail,
                content: unlinkedPATemplateContent
            );

            var unlinkedPATemplateParametersRoot = new UnlinkedPATemplateParametersRoot(data: unlinkedPATemplateParameters);
            return unlinkedPATemplateParametersRoot;
        }

        private static TemplatedPAComponentParametersRoot GetTemplatedPAComponentParametersRoot(
            List<PAIdentifier> paAccounts, List<PAIdentifier> paBenchmarks,
            PADateParameters paDates, List<PACalculationColumn> columns, List<PACalculationGroup> groups, string parentTemplateId)
        {
            var templatedPAComponentData = new PAComponentData(
                accounts: paAccounts,
                benchmarks: paBenchmarks,
                groups: groups,
                columns: columns,
                dates: paDates,
                currencyisocode: CurrencyISOCode,
                componentdetail: ComponentDetail
            );

            var templatedPAComponentParameters = new TemplatedPAComponentParameters(
                directory: TemplatedPAComponentDirectory,
                parentTemplateId: parentTemplateId,
                description: TemplatedPAComponentDescription,
                componentData: templatedPAComponentData
            );

            var templatedPAComponentParametersRoot =
                new TemplatedPAComponentParametersRoot(data: templatedPAComponentParameters);

            return templatedPAComponentParametersRoot;
        }

        private static void PrintResult(ObjectRoot result)
        {
            Console.WriteLine("Calculation Result");

            // converting the data to Package object
            var stachBuilder = StachExtensionFactory.GetRowOrganizedBuilder();
            var stachExtension = stachBuilder.SetPackage(result.Data).Build();
            // To convert package to 2D tables.
            var tables = stachExtension.ConvertToTable();

            Console.WriteLine(tables[0]);
        }
    }
}
Changelog

Version 3

Summary

  • Version 3.16.0 - Released on 09/23/2024
  • Version 3.15.7 - Released on 06/24/2024
  • Version 3.15.6 - Released on 05/27/2024
  • Version 3.15.5 - Released on 03/25/2024
  • Version 3.15.4 - Released on 02/26/2024
  • Version 3.15.3 - Released on 12/18/2023
  • Version 3.15.2 - Released on 11/27/2023
  • Version 3.15.1 - Released on 10/25/2023
  • Version 3.15.0 - Released on 09/25/2023
  • Version 3.14.1 - Released on 08/21/2023
  • Version 3.14.0 - Released on 07/24/2023
  • Version 3.13.0 - Released on 04/25/2023
  • Version 3.12.0 - Released on 01/23/2023
  • Version 3.11.1 - Released on 11/21/2022
  • Version 3.11.0 - Released on 06/06/2022
  • Version 3.10.0 - Released on 04/18/2022
  • Version 3.9.1 - Released on 02/14/2022
  • Version 3.9.0 - Released on 11/22/2021
  • Version 3.8.0 - Released on 10/21/2021
  • Version 3.7.0 - Released on 09/20/2021
  • Version 3.6.0 - Released on 08/23/2021
  • Version 3.5.0 - Released on 08/06/2021
  • Version 3.4.2 - Released on 07/28/2021
  • Version 3.4.1 - Released on 07/19/2021
  • Version 3.3.2 - Released on 07/12/2021
  • Version 3.3.1 - Released on 06/28/2021
  • Version 3.3.0 - Released on 06/07/2021
  • Version 3.2.0 - Released on 05/21/2021
  • Version 3.1.2 - Released on 04/21/2021
  • Version 3.1.1 - Released on 03/20/2021
  • Version 3.1.0 - Released on 03/18/2021
  • Version 3.0.0 - Released on 02/10/2021

Functionality Additions

  • Added new endpoits for FactSet’s AI generated PA portfolio commentary [v3.16.0]
  • Support start date for snapshot type of report in PA component details endpoint. [v3.15.0]
  • Added support for vault holdings mode for PA v3 calculation. [v3.14.0]
  • Support component detail GROUPSALL to get all levels of the group in response for PA v3. [v3.14.0]
  • Updated the Groups endpoint response to fetch definition from GSM. [v3.13.0]
  • Added additional functionality for clients to get the Account and Benchmark details when a component Id is given. [v3.13.0]
  • Added security headers to the response headers. [v3.12.0]
  • Added new GET ALL Calculations endpoint. [v3.11.0]
  • Added support to override the grouping frequency for PA calculation. [v3.10.0]
  • Added a new GET endpoint to return all the available pricing source information, and support the pricing source set at the time of the creation of PA calculation. [v3.10.0]
  • Calculation points will be returned in the GET status endpoints once the calculation completes successfully. [v3.9.0]
  • Improved performance through concurrency [v3.8.0]
  • New field “path” in "GET by Component ID" endpoint response [v3.7.0]
  • New cache-control headers “Age” and “Last-Modified” [v3.6.0]
  • Deprecated "ContentOrganization" and "ContentType" in favor of "StachContentOrganization" and "CalculationFormat" [v3.3.1]
  • Support cache-control headers max-age, max-stale, max-stale & max-age [v3.1.0]
  • Initial Release [v3.0.0]

Changes

  • New "data" top-level object in request and response body
  • New (optional) "meta" top-level object in request and response body
  • STACH v2 returned in results
  • Cache control functionality
  • GET analytics/engines/{engine}/v3/calculations/{id} - now returns calculation parameters instead of calculation status
  • Updated lookup endpoint URLs

Bug Fixes

  • Fix multiport calculation issue where all the account in the request was not considered during calculation. [v3.15.7]
  • Fixed points issue for PA, SPAR, VAULT (v3) [v3.15.6]
  • Improvised error messages for batch calculation 404 scenarios (v2 and v3) [v3.15.6]
  • Without disclosing the internal details we improvised the conversion failure error messages (v3) [v3.15.6]
  • Allowing 10000 model validation errors for engines apis. [v3.15.5]
  • Not to allow running calculation if the report has multi horizon date set and no date is provided in the request body. [v3.15.4]
  • Added example value for long-running request header [v3.15.3]
  • Implemented retry logic when redistimeoutexception is caught while storing the batch response data in Redis for the particular period. [v3.15.2]
  • Corrected the schema documentation of the getAllCalculation endpoint to make it depict the exact output. [v3.15.1]
  • Implemented Exponential backoff Retries to Batch Service. [v3.15.0]
  • Do not crash if the account doesn't have any termination date set but force-end-date-before-termination settings are set for the document. [v3.15.0]
  • Minor bug fix to convert defaults to examples for PA Lookups endpoints. [v3.14.1]
  • Removed mandatory field requirement for pageNumber in GET All Calculations endpoint [v3.14.1]
  • Minor bug fix in case of 404 error response code for DELETE Endpoint.[v3.14.1]
  • Blocked calc-id generation in case of 429 error response code. [v3.14.0]
  • Minor bug fix to support inception date in GET Date endpoint for PA [v3.12.0]
  • Minor bug fix to pass the rolling period from document settings [v3.11.1]
  • Minor bug fixes [v3.11.0]
  • Minor bug fixes [v3.9.1]
  • Fixed an issue where progress percentage on calculation units was not included in POST or GET requests on Engines API [v3.8.0]
  • Supporting SECURITIES in the component detail option [v3.7.0]
  • Improved error messaging for Invalid component id [v3.6.0]
  • Minor bug fixes for a multi-unit calculation [v3.5.0]
  • Minor bug fixes [v3.4.1]
  • Fix for the decoding of v3 Document’s endpoint. [v3.3.2]
  • Fix to support row and column STACH content organizations [v3.3.2]
  • Minor fixes for the SimplifiedRow STACH v2 format v[3.0.0]

v2

Summary

  • v2.9.0 - Released on 12/15/2020
  • v2.8.2 - Released on 10/28/2020
  • v2.8.1 - Released on 10/06/2020
  • v2.8.0 - Released on 08/27/2020
  • v2.7.0 - Released on 08/13/2020
  • v2.6.2 - Released on 07/23/2020
  • v2.6.1 - Released on 07/09/2020
  • v2.6.0 - Released on 06/25/2020
  • v2.5.1 - Released on 04/19/20
  • v2.5.0 - Released on 03/26/20
  • v2.4.1 - Released on 02/27/20
  • v2.4.0 - Released on 01/16/20
  • v2.3.0 - Released on 12/12/19
  • v2.2.0 - Released on 11/25/19
  • v2.1.0 - Released on 09/26/19
  • v2.0.0 - Released on 08/15/19 (Combined PA Engine API v1 and PA Batch API v1 into PA Engine API v2)

Functionality Additions

  • STACH v2.0 [v2.9.0]
  • Support for EXT holdings mode [v2.6.2]
  • Check to limit portfolio*benchmark combinations in a single calculation unit to a maximum of 50 [v2.6.0]
  • Better logging [v2.5.1]
  • Parallel execution of PA multiport calculations [v2.5.0]
  • Performance improvements [v2.4.1]
  • Renamed points to units and removed 25-unit limit per request. Points for billing will be handled asynchronously in the backend [v2.3.0]
  • Added error message when a calculation unit fails due to invalid parameters [v2.2.0]
  • Performance improvement [v2.2.0]
  • Added option to specify holdings mode for accounts when using ‘Run Multiple Calculations’ endpoint [v2.1.0]
  • Added ‘holdingsmode’ and ‘snapshot’ (boolean) properties in ‘Component Settings Lookup’ endpoint [v2.1.0]
  • Added calculation metadata to the result pickup endpoint. The properties include settings used in ‘Run Multiple Calculations’ endpoint and account/benchmark defaults [v2.1.0]
  • Initial release [v2.0.0]

Changes

  • Renamed ‘pointscount’ property to ‘points’ in ‘Get Calculation Status By ID’ and ‘Get All Active Calculations’ endpoints. Points count related response headers were also updated accordingly in ‘Run Multiple Calculations’ endpoint [v2.1.0]
  • Renamed ‘defaultAccounts’, ‘defaultBenchmarks’ and ‘currency’ properties to ‘accounts’, ‘benchmarks’ and ‘currencyisocode’ properties respectively in ‘Component Settings Lookup’ endpoint [v2.1.0]
  • Replaced response data model in ‘Currency Lookup’ endpoint from an array of objects to dictionary of key/value pairs to be consistent with other lookup endpoints [v2.1.0]

Bug Fixes

  • Return 503 on Rate limit transient errors [v2.9.0]
  • Sandbox: Returning Created (201) instead of Success (200) for interactive POST [v2.8.2]
  • Fixing a bug causing a crash while cancelling a calculation [v2.8.1]
  • Fixing minor bug causing a crash [v2.8.1]
  • Return 400 on null values in Accounts [v2.8.0]
  • Fix bug in rate limiting [v2.8.0]
  • Cache control header fix [v2.6.0]
  • Rate limiting header fix [v2.6.0]
  • Null checks for Benchmarks in POST body [v2.5.1]
  • Minor bug fixes [v2.5.1]
  • Composite grouping override returns valid strings [v2.4.1]
  • Improved the description of the dates translation endpoint [v2.3.0]
  • Improved error handling for invalid requests [v2.2.0]
  • Consistent Component ID's irrespective of document path formatting and casing [v2.2.0]

v1 (Retired on 10/31/19)

Summary

  • v1.7.8 - Released on 09/27/19
  • v1.7.7 - Released on 09/27/19
  • v1.7.6 - Released on 09/27/19
  • v1.7.5 - Released on 09/06/19
  • v1.7.4 - Released on 09/06/19
  • v1.7.3 - Released on 08/15/19
  • v1.7.2 - Released on 07/16/19
  • v1.7.1 - Released on 06/13/19
  • v1.7.0 - Released on 04/22/19
  • v1.6.1 - Released on 04/10/19
  • v1.6.0 - Released on 03/27/19
  • v1.5.0 - Released on 02/28/19
  • v1.4.0 - Released on 01/29/19
  • v1.3.0 - Released on 11/29/18
  • v1.2.0 - Released on 10/19/18
  • v1.1.0 - Released on 10/12/18
  • v1.0.0 - Released on 08/30/18

Functionality Additions

  • Retired Vault endpoints in favor of Vault API [v1.7.3]
  • Added incoming rate limit rules for user-serials on trial [v1.7.1]
  • Added account listing endpoint to list account files, composite files and sub-directories in a given directory [v1.7.0]
  • Added document listing endpoint to list PA3 documents in a given directory [v1.7.0]
  • Increased the maximum column count within a PA component to be computed per calculation from 25 to 40 [v1.5.1]
  • Added the option to override columns in a PA component through Calculations API’s Run Calculation endpoint [v1.5.0]
  • Added an endpoint to list all available FactSet-defined and client-defined columns [v1.5.0]
  • Added an endpoint to list all available column statistics that can be applied to a column when overriding in a PA Component through Calculations API’s Run Calculation endpoint [v1.5.0]
  • Returning non-single date frequency components in the components lookup endpoints [v1.4.0]
  • Added additional validations for account and benchmark in the create calculation endpoint [v1.4.0]
  • Added Get All Outstanding Calculations endpoint to fetch progress of all previously requested calculations [v1.3.0]
  • Added Rate Limit response headers to Run Calculation and Run Vault Calculation endpoints [v1.3.0]
  • Added support for Accept request header to all HTTP GET endpoints [v1.3.0]
  • Added API Keys as an additional authentication methodology. One-Time Password (OTP) authentication will be phased out eventually [v1.2.0]
  • [Alpha Release] Added Calculation API for Vault data [v1.1.0]
  • [Alpha Release] Added Helper APIs to lookup Vault configuration(s) by user-serial and by Vault identifier [v1.1.0]
  • Added the option to override groups in a PA component through Calculations API’s Run Calculation endpoint [v1.0.0]
  • Added Helper API for groups lookup [v1.0.0]
  • Added error reasons in response body for responses greater than or equal to HTTP status code 400 [v1.0.0]
  • Added API endpoint to fetch a component’s calculation settings [v1.0.0]
  • Added Api-Supported-Versions response header to describe all API versions supported by a given endpoint [v1.0.0]

Changes

  • Hostname for all endpoints has been changed from “ondemand.factset.com” to “api.factset.com” [v1.0.0]
  • Get Calculation Result Endpoint has been broken down into Get Calculation Status and Get Calculation Result endpoints [v1.0.0]
  • Run Calculation endpoint’s “documentname” property has been retired. Component identifiers will need to be fetched from ‘Fetch Component Settings’ endpoint [v1.0.0]

Bug Fixes

  • Fixed rate limit timeout from 30 milliseconds to 30 seconds [v1.7.2]
  • Fixed error reason when providing incorrect version [v1.3.0]
  • Non-successful responses (HTTP status codes >= 300) will no longer fill up a user-serial’s quota [v1.2.0]