In the previous post in this series, we introduced the True North Data Platform and how you can sign up and try our APIs. In this post we’ll look at how you can validate an XBRL file and then give an example of the powerful functionality the TNDP can offer.

The TNDP provides you with a selection of “validation profiles”, which determine the XBRL taxonomy to validate against along with the filing rules and any additional processing, such as conversion to a different file format. The first step to submitting a file to the TNDP is getting the ID of the validation profile you need. Armed with this, you’re ready to submit your first filing to the TNDP!

The program below requires an example Solvency II filing (you can download one here) and validates it against a profile which includes the Bank of England’s filing rules (and will also run the formula rules in the taxonomy). It then polls the TNDP and outputs the validation results upon completion. For table based reports (such as Solvency II and COREP), it will also retrieve an Excel rendering of the XBRL and validation results.

#!/usr/bin/env python3

SERVICE_CLIENT_NAME = "(insert yours)"
SERVICE_CLIENT_SECRET = "(insert yours)"
BASE_URL = 'https://api.labs.corefiling.com/'
XBRL_FILING = '/path/to/SampleSIIFiling.xml' #insert path to your filing here

import re
import json
import requests
import time
from os.path import expanduser
from requests.auth import HTTPBasicAuth

def main():
    # Authenticate and obtain access token. More usually an OAuth2 client library would be used
    at_response = json.loads(requests.post('https://login.corefiling.com/auth/realms/platform/protocol/openid-connect/token',
                                        data={'grant_type':'client_credentials'},
                                        auth=HTTPBasicAuth(SERVICE_CLIENT_NAME, SERVICE_CLIENT_SECRET)).text)
    access_token = at_response['access_token']
    auth_header = {'Authorization': 'Bearer ' + access_token}

    # Look up validation profile by name
    def get_profile_id():
        req = requests.get(BASE_URL + 'document-service/v1/categories/validation', headers=auth_header)
        req.raise_for_status()
        profiles_by_name = {}
        for profile in json.loads(req.text)['profiles']:
            profiles_by_name[profile["name"]] = profile["id"]
        target_profile = profiles_by_name['Solvency II 2.2.0 hotfix / Bank of England Filing Rules']
        return target_profile

    # Upload filing to target validation profile - multipart POST request
    def post_filing():
         files = {'file': open(XBRL_FILING, 'rb')}
         upload_req = requests.post(BASE_URL + 'document-service/v1/filings?validationProfile=' + get_profile_id(), files=files, headers=auth_header)
         upload_req.raise_for_status()
         return json.loads(upload_req.text)['versions'][0]['id']

    # Poll until processing has completed - this depends on filing size but usually takes at least 30 seconds
    filing_version_id = post_filing()
    print ('Document is processing, please wait..')
    done = False
    while not done:
        filing_req = requests.get(BASE_URL + 'document-service/v1/filing-versions/' + filing_version_id, headers=auth_header)
        filing_req.raise_for_status()
        filing = json.loads(filing_req.text)
        done = "DONE" == filing["status"]
        time.sleep(5)

    # Once processing has completed, results can be accessed from other APIs, e.g. validation messages:
    val_results_req = requests.get(BASE_URL + 'validation-service/v1/filing-versions/%s/issues/' % filing_version_id, headers=auth_header)
    val_results_req.raise_for_status()
    for message in (json.loads(val_results_req.text)):
        print (message['validationMessage']['errorCode'] + '\t' + message['validationMessage']['messageDetail'])

    # Get the output document ID for an Excel rendering and then download the Excel file
    outputs_req = requests.get(BASE_URL + 'document-service/v1/filing-versions/' + filing_version_id, headers=auth_header)
    outputs_req.raise_for_status()
    documents_by_category = {}
    for document in json.loads(outputs_req.text)['documents']:
        documents_by_category[document["category"]] = document["id"]
    excel_req = requests.get(BASE_URL + 'document-service/v1/documents/%s/content' %documents_by_category['excel-rendering'], headers=auth_header)
    excel_req.raise_for_status()
    with open('TNDP-API-ExcelOutput.xlsx', 'wb' ) as excel_file:
        excel_file.write(excel_req.content)

if __name__=='__main__':
    main()

To request an API key to try this yourself, contact us. We’d also love to hear from you if you have ideas for applications you’d like to see on the TNDP!

To explore more API features, see Part 3.