--- title: "Monitoring custom integrations by logging to the Integrations dashboard" slug: "external-client-application" updated: 2026-09-10T10:05:30Z published: 2026-09-10T10:05:30Z canonical: "support.lusid.com/external-client-application" --- > ## Documentation Index > Fetch the complete documentation index at: https://support.lusid.com/llms.txt > Use this file to discover all available pages before exploring further. # Monitoring custom integrations by logging to the Integrations dashboard FINBOURNE offers an **External client application** integration type, enabling you to externally host and manage applications that can interact with the LUSID [**Integrations logging dashboard**](/v1/docs/setting-up-an-integration#monitoring-integration-runs). This gives you the flexibility to develop integrations tailored to your preferred technology stack, while maintaining logging visibility through LUSID’s infrastructure. You can build bespoke LUSID integrations for the applications that matter to you by having your external application call the API and push your logs to LUSID. Note that this integration type is a tool for storing and viewing logs for your custom processes - it does not upsert any LUSID investment data. > [!NOTE] > Prerequisites > > Before getting started, ensure you have: > > - [x] An active contract with the application you wish to build an integration for (if necessary) > - [x] [Contacted your FINBOURNE representative](/v1/docs/how-do-i-use-the-support-centre) to enable the **External client application** integration type in your LUSID domain ## Setting up and running the integration Each custom integration must have a unique integration instance ID. You’ll pass the integration instance ID into [each run of your integration](/v1/docs/external-client-application#running-the-integration). ### Step 1: Create an integration instance LUSID web appREST API 1. Navigate to **Integrations > Dashboard** 2. Select **Create instance**. 3. Specify the following: - **Name**: A friendly name for this integration instance - **Description**: A detailed description of the instance 4. Click **Save** to create the integration instance. Note the **Instance ID**, which you’ll need to run the integration. ![](https://cdn.document360.io/d575ad81-c0ed-4980-bbd1-d59ac5c3de82/Images/Documentation/image-YFCXWESS.png?sv=2026-02-06&spr=https&st=2026-11-09T07%3A08%3A18Z&se=2026-11-09T08%3A08%3A18Z&sr=c&sp=r&sig=Z%2F0NnXsuPz65azscW%2BUeAMRFPzkmM1Xhw3gjJ308LSs%3D) Call the [CreateInstance](https://www.lusid.com/docs/api/horizon/endpoints/integrations/CreateInstance) API, passing in: - `integrationType`: `external-client-application` - `name`: A friendly name for your custom integration - `description`: A friendly description for your custom integration - `enabled`: `true` For example: ```json curl -X POST "https://.lusid.com/horizon/api/integrations/instances"  -H "Authorization: Bearer "  -H "Content-Type: application/json"  -d "{    "integrationType": "external-client-application",    "name": "Custom Integration 1",    "description": "Logs for Custom Integration 1",    "enabled": true,    "triggers": [      {        "type": "",        "cronExpression": "",        "timeZone": ""      }   ],    "details": {}  }" ``` An example response is shown below. Note the integration instance `id`, which you’ll need to run the integration. ```json {  "id": "04057004-a881-44bc-8af9-f95005f511bf" } ``` ### Step 2: Kick off a run of the integration instance To insert information to the integration logs, you must first kick off a run of the integration instance and retrieve the run ID. LUSID web appREST API 1. Navigate to **Integrations > Dashboard**. 2. Click **Menu > Run** on your integration instance. ![](https://cdn.document360.io/d575ad81-c0ed-4980-bbd1-d59ac5c3de82/Images/Documentation/image-9UILGTG7.png?sv=2026-02-06&spr=https&st=2026-11-09T07%3A08%3A18Z&se=2026-11-09T08%3A08%3A18Z&sr=c&sp=r&sig=Z%2F0NnXsuPz65azscW%2BUeAMRFPzkmM1Xhw3gjJ308LSs%3D) 3. Navigate to **Integrations > Runs**. 4. Locate the run and note the unique **Run ID**. ![](https://cdn.document360.io/d575ad81-c0ed-4980-bbd1-d59ac5c3de82/Images/Documentation/image-R4WU369A.png?sv=2026-02-06&spr=https&st=2026-11-09T07%3A08%3A18Z&se=2026-11-09T08%3A08%3A18Z&sr=c&sp=r&sig=Z%2F0NnXsuPz65azscW%2BUeAMRFPzkmM1Xhw3gjJ308LSs%3D) Call the [ExecuteInstance](https://www.lusid.com/docs/api/horizon/endpoints/integrations/ExecuteInstance) API, passing in the instance `id` from [step 1](/v1/docs/external-client-application#step-1-create-an-integration-instance). For example: ```bash curl -X POST "https://.lusid.com/horizon/api/integrations/instances/04057004-a881-44bc-8af9-f95005f511bf/execute"  -H "Authorization: Bearer "  -H "Content-Type: application/json" ``` An example response is shown below. Note the unique `executionId`, which you’ll need to create logs in LUSID. ```json {  "executionId": "7a76a76e-55a3-4655-8727-474c340dea06" } ``` ### Step 3: Configure your integration to upsert logging information via InsertExternalLogs API The final stage of the setup is highly customisable. It’s up to you to map your data to the appropriate log status. To get the most out of setting up logging in this way, it’s useful to understand what each status log indicates (though it remains customisable for you and your requirements). [Read about what each log status means.](/v1/docs/setting-up-an-integration#viewing-and-troubleshooting-all-runs) To insert logs for your integration to LUSID, you must configure your application to call the [InsertExternalLogs](https://www.lusid.com/docs/api/horizon/endpoints/logs/InsertExternalLogs) API, passing in the integration `instanceId` ([step 1](/v1/docs/external-client-application#step-1-create-an-integration-instance)) and `runId` ([step 2](/v1/docs/external-client-application#step-2-kick-off-a-run-of-the-integration-instance)) as parameters, and the following in the request body: - An identifying `logId` - A `logLevel` specifying a log type of `Payload` or `Resource` - A `logStatus`, indicating the current state of the integration, for example `PayloadExtracted`, `ResourceLoaded`, or `ResourceFailed` - A `timestamp` for the log - Optionally, any of the following supplementary fields: - An identifying `parentLogId` to group one or more `logId`s together - `sourceRecordType` - `sourcePrimaryIdType` - `sourcePrimaryIdValue` - `targetRecordType` - `targetPrimaryIdType` - `targetPrimaryIdValue` - `targetRecordAction` - `message` - `messageType` For example, to insert logs for the integration instance run from steps 1 and 2: ```json curl -X POST "https://.lusid.com/horizon/api/logs/04057004-a881-44bc-8af9-f95005f511bf/7a76a76e-55a3-4655-8727-474c340dea06"  -H "Authorization: Bearer "  -H "Content-Type: application/json"  -d "{  "logs": [    {      "logid": 1,      "parentlogid": 1,      "loglevel": "Payload",      "logstatus": "PayloadAcquired",      "timestamp": "2025-01-22 23:12:13.1234"    },    {      "logid": 2,      "parentlogid": 1,      "loglevel": "Payload",      "logstatus": "PayloadExtracted",      "timestamp": "2025-01-22 23:12:13.1234"    },    {      "logid": 3,      "parentlogid": 2,      "loglevel": "Resource",      "logstatus": "ResourceLoaded",      "sourceprimaryidtype": "Ticker",      "sourceprimaryidvalue": "MSFT",      "targetprimaryidvalue": "MSFT",      "timestamp": "2025-01-22 23:12:13.1234"    },    {      "logid": 4,      "parentlogid": 2,      "loglevel": "Resource",      "logstatus": "ResourceLoaded",      "sourceprimaryidtype": "Ticker",      "sourceprimaryidvalue": "AAPL",      "targetprimaryidvalue": "AAPL",      "timestamp": "2025-01-22 23:12:13.1234"    },  ] }" ``` After successfully inserting the logs to LUSID, you can view them in the LUSID web app via **Integrations > Runs >** {Run ID}. ![](https://cdn.document360.io/d575ad81-c0ed-4980-bbd1-d59ac5c3de82/Images/Documentation/image-SUT8DB8E.png?sv=2026-02-06&spr=https&st=2026-11-09T07%3A08%3A18Z&se=2026-11-09T08%3A08%3A18Z&sr=c&sp=r&sig=Z%2F0NnXsuPz65azscW%2BUeAMRFPzkmM1Xhw3gjJ308LSs%3D) ### Step 3: Update the integration instance run status To update the run status to **Completed**, you must end the integration run by calling the [StopInstanceExecution](https://www.lusid.com/docs/api/horizon/endpoints/runs/StopInstanceExecution) API, passing in the integration `instanceId` and `runId`: ```bash curl -X POST "https://.lusid.com/horizon/api/runs/04057004-a881-44bc-8af9-f95005f511bf/7a76a76e-55a3-4655-8727-474c340dea06/stop"  -H "Authorization: Bearer "  -H "Content-Type: application/json" ``` ![](https://cdn.document360.io/d575ad81-c0ed-4980-bbd1-d59ac5c3de82/Images/Documentation/image-2AR3A6Q3.png?sv=2026-02-06&spr=https&st=2026-11-09T07%3A08%3A18Z&se=2026-11-09T08%3A08%3A18Z&sr=c&sp=r&sig=Z%2F0NnXsuPz65azscW%2BUeAMRFPzkmM1Xhw3gjJ308LSs%3D) ## Monitoring and troubleshooting integration runs [See how to monitor integrations and troubleshoot general issues.](/v1/docs/setting-up-an-integration#monitoring-integration-runs) ## Example: Using the Horizon Python SDK to create a log per instrument upserted to LUSID The following example adds friendly logging to the instrument upsert operation introduced in [this Jupyter notebook](https://github.com/finbourne/finbourne-notebooks/blob/main/V2/university/T01001-LUSID-core-concepts/T01001-LUSID-core-concepts.ipynb). The example uses the [Horizon](https://github.com/finbourne/horizon-sdk-python) and [LUSID](https://github.com/finbourne/lusid-sdk-python) Python SDKs to: 1. Perform a one-time creation of an integration instance. 2. Upsert the instruments to LUSID, alongside a log for each instrument. 3. Except any errors. *****Click to expand Python script***** ```python # Set up LUSID and Horizon API import pandas as pd import logging logging.basicConfig(level = logging.INFO) import lusid as lu import lusid.api as la import lusid.models as lm from lusidjam import RefreshingToken import lusid.extensions as le from finbourne_sdk_utils.lpt.lpt import to_date lusid_config_loaders=[    le.ArgsConfigurationLoader(api_url="https://.lusid.com/api" ,access_token = RefreshingToken(), app_name = "LusidJupyterNotebook"),    le.EnvironmentVariablesConfigurationLoader()] lusid_api_factory = le.SyncApiClientFactory(config_loaders=lusid_config_loaders) # Set pandas display options pd.set_option("display.max_columns", None) pd.set_option("display.max_rows", None) pd.options.display.float_format = "{:,.2f}".format import finbourne_horizon as horizon from finbourne_horizon.models.external_log_insertion_request import ExternalLogInsertionRequest from finbourne_horizon.models.external_log_record import ExternalLogRecord from finbourne_horizon.exceptions import ApiException from finbourne_horizon.extensions.configuration_options import ConfigurationOptions from finbourne_horizon.models import * from pprint import pprint horizon_config_loaders=[    horizon.ArgsConfigurationLoader(api_url = "https://.lusid.com/horizon", access_token = RefreshingToken(), app_name = "LusidJupyterNotebook"),    horizon.EnvironmentVariablesConfigurationLoader()    ] horizon_api_factory = horizon.SyncApiClientFactory(config_loaders=horizon_config_loaders) integrations_api = horizon_api_factory.build(horizon.IntegrationsApi) logs_api = horizon_api_factory.build(horizon.LogsApi) runs_api = horizon_api_factory.build(horizon.RunsApi) # Obtain the Instruments API instruments_api=lusid_api_factory.build(la.InstrumentsApi) # Read assets into pandas dataframe from securities file and show it assets_df = pd.read_csv("data/assets.csv", keep_default_na = False) display(assets_df) ``` One-time setup of integration instance: ```python # ONE-TIME SETUP: Create integration instance (run this cell only once, then comment it out) triggers = [Trigger(type = "", cronExpression="", time_zone="")] try:     create_instance_request = CreateInstanceRequest(         name="Instrument Upsert Integration",         description="Logs for instrument upsert operations from Jupyter notebook",         integration_type="external-client-application",         triggers = triggers,         details = "",         enabled=True     )         instance_response = integrations_api.create_instance(create_instance_request=create_instance_request)     instance_id = instance_response.id     print(f"Created integration instance with ID: {instance_id}")     print("IMPORTANT: Save this instance_id and use it in the next cell") except horizon.ApiException as e:     print(f"Error creating instance: {e}") ``` Kick off a run of the instance and log or except the results: ```python # After running the above once, paste your instance_id here: instance_id = "b13f175e-c50a-443d-ba0d-de26b5ad8f0b"  # Replace with your actual instance ID print(f"Using instance ID: {instance_id}") # Create a dictionary of instrument definitions from asset data definitions = {} # Iterate over each row in the assets dataframe for index, asset in assets_df.iterrows():        # Map identifier columns to case-sensitive LUSID identifier names          identifiers = {        # Unique identifiers        "Figi": lm.InstrumentIdValue(value = asset["figi"]),        "ClientInternal": lm.InstrumentIdValue(value = asset["internal_id"]),        # Non-unique identifiers        "Isin": lm.InstrumentIdValue(value = asset["isin"]),        "Ticker": lm.InstrumentIdValue(value = asset["ticker"])      }                                          # Model equities    if asset["security_type"] == "equity":        # Create definitions        definitions[asset["instrument_name"]] = lm.InstrumentDefinition(            name = asset["instrument_name"],            identifiers = identifiers,            definition = lm.Equity(                instrument_type = "Equity",                dom_ccy = asset["currency"],                identifiers = {}            )        )    # Model bonds    elif asset["security_type"] == "govt bond":        definitions[asset["instrument_name"]] = lm.InstrumentDefinition(            name = asset["instrument_name"],            identifiers = identifiers,            definition = lm.Bond(                instrument_type = "Bond",                start_date = "2021-01-01T00:00:00Z",                maturity_date = asset["maturity_date"],                dom_ccy = asset["currency"],                flow_conventions = lm.FlowConventions(                    currency = asset["currency"],                    payment_frequency = "6M",                    day_count_convention = "ActualActual",                    roll_convention = "NoAdjustment",                    payment_calendars = [],                    reset_calendars = [],                    settle_days = 0,                    reset_days = 0                ),                principal = 1,                coupon_rate = float(asset["coupon"])            )        ) # Start an integration run to get a run ID try:    run_response = integrations_api.execute_instance(instance_id=instance_id)    run_id = run_response.execution_id    print(f"Started integration run with ID: {run_id}") except horizon.ApiException as e:    print(f"Error starting integration run: {e}")    raise try:    # Upsert instruments to a custom scope in LUSID    upsert_instruments_response = instruments_api.upsert_instruments(        request_body = definitions,        scope = "external-client-application",    )        # Transform API response to a dataframe    upsert_instruments_response_df = lusid_response_to_data_frame(        list(upsert_instruments_response.values.values())    )        # Display the results    display(upsert_instruments_response_df[["name", "lusidInstrumentId"]])        # Log successful upserts - one log entry per instrument    logs_to_insert = []        for index, row in upsert_instruments_response_df.iterrows():        message = f"Successfully upserted instrument: {row['name']}"        log_record = ExternalLogRecord(            logid=index + 1,            loglevel="Resource",            logstatus="ResourceLoaded",            sourcerecordtype="Instrument",            sourceprimaryidtype="InstrumentName",            sourceprimaryidvalue=row["name"],            targetprimaryidtype="LusidInstrumentId",            targetprimaryidvalue=row["lusidInstrumentId"],            targetrecordtype="LUSID",            targetrecordaction="Upsert",            message=message[:1024],            timestamp="2025-01-22 23:12:13.1234"        )        logs_to_insert.append(log_record)        # Insert logs to Horizon    external_log_insertion_request = ExternalLogInsertionRequest(logs=logs_to_insert)        logs_response = logs_api.insert_external_logs(        instanceid=instance_id,        runid=run_id,        external_log_insertion_request=external_log_insertion_request    )        print(f"Successfully logged {len(logs_to_insert)} instrument upserts to Horizon")        # Complete the integration run successfully    try:        runs_api.stop_instance_execution(instance_id=instance_id, run_id=run_id)        print(f"Stopped integration run {run_id}")    except horizon.ApiException as e:        print(f"Warning: Could not stop integration run: {e}")     except lu.ApiException as e:    # Log the failure    print(f"Failed to upsert instruments: {e}")        error_message = f"Failed to upsert instruments to LUSID: {str(e)}"    error_log = ExternalLogRecord(        logid=1,        loglevel="Resource",        logstatus="ResourceFailed",        sourcerecordtype="InstrumentUpsert",        message=error_message[:1024],        timestamp="2025-01-22 23:12:13.1234"    )        external_log_insertion_request = ExternalLogInsertionRequest(logs=[error_log])        try:        logs_response = logs_api.insert_external_logs(            instanceid=instance_id,            runid=run_id,            external_log_insertion_request=external_log_insertion_request        )        print("Failure logged to Horizon")                # Stop the integration run        runs_api.stop_instance_execution(instance_id=instance_id, run_id=run_id)        print(f"Stopped integration run {run_id}")            except horizon.ApiException as log_error:        print(f"Failed to log error to Horizon: {log_error}")        # Re-raise the original exception    raise except Exception as e:    # Catch any other unexpected errors    print(f"Unexpected error: {e}")        error_message = f"Unexpected error during instrument upsert: {str(e)}"    error_log = ExternalLogRecord(        logid=1,        loglevel="Payload",        logstatus="PayloadFailed",        sourcerecordtype="InstrumentUpsert",        message=error_message[:1024],        timestamp="2025-01-22 23:12:13.1234"    )        external_log_insertion_request = ExternalLogInsertionRequest(logs=[error_log])        try:        logs_response = logs_api.insert_external_logs(            instanceid=instance_id,            runid=run_id,            external_log_insertion_request=external_log_insertion_request        )        print("Failure logged to Horizon")                # Stop the integration run        runs_api.stop_instance_execution(instance_id=instance_id, run_id=run_id)        print(f"Stopped integration run {run_id}")            except horizon.ApiException as log_error:        print(f"Failed to log error to Horizon: {log_error}")        raise ``` Once we run the script, we can check the logs by navigating to **Integrations > Runs > {Run ID}**: ![](https://cdn.document360.io/d575ad81-c0ed-4980-bbd1-d59ac5c3de82/Images/Documentation/image-ZUGNMS4G.png?sv=2026-02-06&spr=https&st=2026-11-09T07%3A08%3A18Z&se=2026-11-09T08%3A08%3A18Z&sr=c&sp=r&sig=Z%2F0NnXsuPz65azscW%2BUeAMRFPzkmM1Xhw3gjJ308LSs%3D)