Working with CURO - A Technical Perspective

4th April 2025 Adam George

At PowerPlanner, we are always looking to reduce the manual labour required to deliver financial advice. Part of this involves alleviating the need to re-enter information into multiple different systems, and so API integration is always something we seek to use to our advantage.

We have had Intelliflo Office API integration for a few years now, which allows users a smooth and efficient experience for submitting cases to PowerPlanner. We are now able to boast that the same capabilities are available for Time 4 Advice CURO users too.

In this post, we present a technical overview of how the API integration works. Hopefully it will be useful for others seeking to interact with T4A CURO via its API as well.

Prerequisites

It's assumed at this point you've already got yourself access to the T4A CURO UAT system (the UAT tenant), and have thus been provided with your client credentials: a client ID and a client secret.

A key point to note with T4A CURO is that the client credentials you're given are coupled to the tenant. This means that you'll get a client ID/secret combination for UAT and then you'll need different credentials to integrate with the production tenant that holds the IFA's data. Production tenant API credentials can be generated on request by contacting your Time4Advice representative.

Authentication and Authorisation

Like the vast majority of JSON APIs out there today, T4A CURO's API uses OAuth to establish trust between the two systems sharing data. However, the means by which a bearer token is established is not by browser redirects as with authorisation code flow. Instead, the API uses client credentials flow - a machine-to-machine mechanism that is a much simpler one-step process.

To get started, with the above prerequisites met, all you need to do is to make a POST request to the authorisation server endpoint (e.g., /Time4Advice.CuroWebAPI/2.2/token) of the target CURO system (tenant) with the following 5 request parameters:

  1. grant_type - this will be "client_credentials" in this case
  2. auth_type - this will be "ThirdParty" in this case
  3. client_id - your unique Client ID that is bound to the CURO tenant
  4. client_secret - the secret string that corresponds to your Client ID
  5. crmUserEmail - the email address of an authorised CURO user on that tenant

Note that you need to set the "Content-Type" request header to "application/x-www-form-urlencoded" for this to work or you will get a "400 Bad Request" back from the authorisation server.

If the request is successful, you will get back a JSON response that looks a bit like this:

	{
	   "access_token":"8AF_EObP1u...(several hundred more characters)...G8unYZdZV",
	   "token_type":"bearer",
	   "expires_in":7199,
	   "CoreWebApi:ClientId":"Ej1AZprDaZYhqTbRmoHeL9",
	   "CoreWebApi:OrganizationName":"curointegrationuat",
	   "CoreWebApi:TokenExpiryTimeUTC":"20250328T175757"
	}

The value of the attribute called access_token is the bearer token to use for authentication going forward.

Please note also that, with this being machine-to-machine, the client ID/secret pair are bound to a particular T4A CURO tenant. This means that, rather inconveniently, when you progress beyond the UAT phase you will need to get a new client ID and client secret that works with the production CURO tenant.

Using the API

Now you've got your bearer token, you can start using it to query the API.

CURO supports a couple of endpoints for this:

  1. /Time4Advice.CuroWebAPI/2.2/api/B2B/retrieveentity - API to select a named entity by matching attributes
  2. /Time4Advice.CuroWebAPI/2.2/api/B2B/fetchxml - API that accepts XML wrapped in JSON to craft a query

Note that the above endpoints work with HTTP POST requests, regardless of the nature of the operation. This is unlike RESTful APIs, where you would intuitively expect an endpoint with a name like "retrieveentity" to take GETs. Both endpoints also respond with JSON documents which, again, may not be intuitive in the case of "fetchxml".

Basic Entity Data Retrieval

To make an API call, issue an HTTP POST to either of the above endpoints with the following request headers:

  • Content-Type: application/json - appropriate even if using the "fetchxml" endpoint
  • Authorization: Bearer access_token - replace "access_token" with your bearer token obtained earlier

With these requests for information being POSTs rather than GETs, the request body contains the query. Here is an example of a POST body used to retrieve account information from the "retrieveentity" endpoint:

	{
	   "entityname": "account",
	   "entityattributes": {"
	      "accountid": "90eeb77f-7cfb-eb11-a841-000d3a7ef302"
	   },
	   "retrieveattributes": [
	      "name", "t4a_accounttype", "address1_postalcode", "t4a_mobilephone"
	   ]
	}

This instructs T4A CURO to find an entity of the named type with the matching entityattributes, so in this case we're retrieving an account by ID - all pretty intuitive stuff! Note that the retrieveattributes array is optional and only needs to be included to control the attributes retrieved. If you omit it then you get all the entity's attributes by default.

An example response to such a query would be JSON that looks a little bit like this:

	{
	  "data": [
	    {
	      "LogicalName": "account",
	      "Id": "90eeb77f-7cfb-eb11-a841-000d3a7ef302"
	      "Attributes": [
	        {
	          "Key": "name", "Value": "Tobias Test-Client"
	        }, {
	          "Key": "t4a_mobilephone", "Value": "07987654321"
	        }, {
	          "Key": "address1_postalcode", "Value": "PR5 9LG"
	        }, {
	          "Key": "t4a_accounttype",
	          "Value": {
	            "Value": 487790000
	          }
	        }, {
	          "Key": "accountid", "Value": "90eeb77f-7cfb-eb11-a841-000d3a7ef302"
	        }
	      ],
	      "FormattedValues": [
	        {
	          "Key": "t4a_accounttype", "Value": "Household"
	        }
	      ],
	      "RelatedEntities": [],
	      "RowVersion": "145377924",
	      "KeyAttributes": []
	    }
	  ]
	}

As you can see, CURO's "retrieveentity" endpoint is a simple and useful way to issue queries by example. However, if you need to bring in details from related entities or issue more complex queries, you will probably need to use the XML-based endpoint instead.

A Word about FetchXML

It's likely there'd be a consensus among seasoned software engineers that the concept of a JSON API that works by taking escaped XML as its input is, to be kind, a bit peculiar.

That said, once you get a feel for it, the XML is really quite expressive, and you can build up some pretty powerful requests that span multiple entities. This POST body snippet, for example, looks up plans that belong to either of two specific accounts, linking each plan back to its owning account and, in turn, to the primary contact associated with the owner of the plan:

	{ 
	   "fetchxml": "<fetch version=\"1.0\" mapping=\"logical\" distinct=\"false\">
	      <entity name=\"t4a_curoholding\">
	         <attribute name=\"t4a_accountid\"/>
	         <attribute name=\"statecode\"/>
	         <attribute name=\"t4a_policynumber\"/>   <!-- Plan/Policy Number -->
	         <attribute name=\"t4a_curoproductid\"/>  <!-- Plan Type -->
	         <attribute name=\"t4a_planstatus\"/>
	         <attribute name=\"t4a_productproviderid\"/>
	         <link-entity name=\"t4a_provider\" from=\"t4a_providerid\" to=\"t4a_productproviderid\" link-type=\"inner\">
	            <attribute name=\"t4a_name\"/>        <!-- Provider Name -->
	            <order attribute=\"t4a_name\" descending=\"false\"/>
	         </link-entity>
	         <link-entity name=\"account\" from=\"accountid\" to=\"t4a_accountid\" link-type=\"outer\">
	            <attribute name=\"primarycontactid\"/>
	            <link-entity name=\"contact\" from=\"contactid\" to=\"primarycontactid\" link-type=\"outer\">
	               <attribute name=\"birthdate\"/>
	            </link-entity>
	         </link-entity>
	         <filter type=\"or\">
	            <condition attribute=\"t4a_accountid\" operator=\"eq\" value=\"primaryCuroClientId\"/>
	            <condition attribute=\"t4a_accountid\" operator=\"eq\" value=\"secondaryCuroClientId\"/>
	         </filter>
	      </entity>
	   </fetch>"
	}

This level of querying would not be possible in a textbook REST API. You would have to issue several GETs to find the information you need and write imperative code to join the information together. So, while it may seem cumbersome and overly verbose at first, T4A CURO's FetchXML capability does have its merits.

Handling Documents

Time4Advice have also equipped CURO with the ability to share documents through the API.

While you can get some document metadata with "retrieveentity" endpoint queries, because CURO stores information about documents across t4a_curodocument and annotation entities, you're better off crafting a "fetchxml" query to get the information you need.

Here is an example of one such request:

	{
	   "fetchxml": "<fetch version=\"1.0\" mapping=\"logical\" distinct=\"false\">
	      <entity name=\"t4a_curodocument\">
	         <attribute name=\"activityid\"/>
	         <attribute name=\"t4a_documenttype\"/>
	         <attribute name=\"t4a_documentdate\"/>
	         <attribute name=\"subject\"/>
	         <attribute name=\"t4a_narrative\"/>
	         <attribute name=\"t4a_mimetype\"/>
	         <attribute name=\"t4a_filesize\"/>
	         <attribute name=\"t4a_accountid\"/>
	         <link-entity name=\"annotation\" from=\"objectid\" to=\"activityid\" link-type=\"inner\">
	            <attribute name=\"filename\"/>
	            <order attribute=\"filename\" descending=\"false\"/>
	         </link-entity>
	         <filter>
	            <condition attribute=\"t4a_accountid\" operator=\"eq\" value=\"curoClientAccountId\"/>
	         </filter>);
	      </entity>
	   </fetch>"
	}

This searches for t4a_curodocument entities that belong to a particular account and links into each corresponding annotation entity to retrieve useful information such as the file name.

That's all the document metadata taken care of, but, to get the actual document data, you will need to obtain a download link.

To do this, you need to make a call to "retrieveentity" for the annotation that corresponds to the document you wish to download. Here's an example:

	{
	   "entityname": "annotation",
	   "entityattributes": {
	      "objectid": "b411e7bd-bb8f-ef11-a862-000d3a7ef33c",
	      "isdocument": true
	   },
	   "retrieveattributes": [
	      "objectid", "filename", "subject", "notetext", "mimetype", "documentbody"
	   ]
	}

The value of the objectid entity attribute in the request is the ID (specifically the activityid) of the t4a_curodocument that you wish to download.

As with the earlier example, the result you get is a set of key-value pairs that correspond to the retrieveattributes you asked for in the request. The one we care about is the documentbody and it looks something like this:

	...
	{
	   "Key": "documentbody",
	   "Value": "https://strcurointegration.blob.core.windows.net/curo ... sp\u003dr\u0026rscd\u003dattachment%3B%20filename%3Dsample-file.pdf"
	},
	...

The value given is a link to download the document. However, its request parameters are encoded to make it compatible with the JSON format. To make a usable URL, you need to replace all occurrences of \u003d with '=' and \u0026 with an ampersand.

Once you've done this, you can issue a GET request to the resultant URL and download the document data.

Conclusion

Time4Advice have implemented a very useful API into CURO. While it's not conventional in a lot of ways, it is powerful and expressive, and once you get used to it it's a pleasure to work with.

CURO also allows much deeper access to information than some other back office systems or CRM tools that are prevalent in the financial planning world. It's possible to retrieve information about reviews, objectives and other "soft" facts, most of which is not made available through APIs into other systems. For customers of PowerPlanner, this means much less duplication of effort and faster paraplanning submissions, leading to clearer cases and happier administrators.

Contact Us to See How Our Technology Could Benefit Your Business

PowerPlanner is the trading style of PowerPlanner Solutions Ltd, a company registered in England and Wales, company number 8743976, limited by shares.

© 2025 PowerPlanner Solutions Ltd. All rights reserved.