APIs – Damien Devaney https://damienpdevaney.co.uk Everything Computer Wed, 07 May 2025 21:48:45 +0000 en hourly 1 https://wordpress.org/?v=7.0 https://damienpdevaney.co.uk/wp-content/uploads/2025/05/cropped-ITmusingFavIcon-2-32x32.png APIs – Damien Devaney https://damienpdevaney.co.uk 32 32 Hubspot Deals and Line Items in Power BI https://damienpdevaney.co.uk/hubspot-deals-and-line-items-in-power-bi/ https://damienpdevaney.co.uk/hubspot-deals-and-line-items-in-power-bi/#respond Fri, 25 Apr 2025 14:04:46 +0000 https://damienpdevaney.co.uk/?p=161 There is allegedly a Hubspot connector for Power BI however I have never got it to work. Even if it does I suspect it would not allow access to deal line items which are buried deep in the Hubspot API.

Deal line items are when you create a Deal and add Products which may be itemised in a Quote.

The goal is to get a table that can be used in Power BI that lists all the deal line items along with their respective companies and deal owners.

The Hubspot API is fairly easy to follow. This is the page dealing with Line Items: https://developers.hubspot.com/docs/guides/api/crm/commerce/line-items

In order to call the API you need to obtain a bearer token from Hubspot.

The overall plan

In Power Query we will get the line items from Hubspot and transform the response into a query table.

Then we do the same with Companies, Deals, Pipelines (the organisational units that contain Deals) and join them altogether in Power Query using the NestedJoin function.

…one slight problem

Hubspot will return 1 page for each API call which only gets us 100 rows. One of the query parameters is PageValue. So we have to do this:

  • Make a PageValue parameter in Power Query to store the current page value.
  • Make a function in Power Query “GetPageFunction-LineItems” that returns one page of line items taking the current page value as an arguent.
  • Make a function “GetAllPagesFunction-LineItems” that iterates through the GetPage function and accumulates the data.
  • Make a AllLineItemsWithDeals table that populates itself using the GetAllPages function.

AllLineItemsWithDeals

Working backwards, The AllLineItemsWithDeals table is defined like this:

let
    InitialPageValue = "1",
    InitialData = #table(type table [results.id = Int64.Type, results.properties.amount = Int64.Type, results.properties.createdate = DateTime.Type, results.properties.hs_lastmodifieddate = DateTime.Type, results.properties.hs_object_id = Int64.Type, results.properties.hs_product_id = Int64.Type, results.properties.quantity = Int64.Type, results.createdAt = DateTime.Type, results.updatedAt = DateTime.Type, results.archived = Logical.Type, paging.next.after = Int64.Type, paging.next.link = Text.Type], {}),
    AllData = #"GetAllPagesFunction-LineItems"(InitialPageValue, InitialData),
    #"Filtered Rows" = Table.SelectRows(AllData, each true),
    #"Sorted Rows" = Table.Sort(#"Filtered Rows",{{"results.id", Order.Ascending}}),
    #"Filtered Rows1" = Table.SelectRows(#"Sorted Rows", each ([getDealIDCustom] <> null)),
    #"Changed Type" = Table.TransformColumnTypes(#"Filtered Rows1",{{"getDealIDCustom", Int64.Type}})
in
    #"Changed Type"

The above creates the table the then uses Table.SelectRows to populate it using the function GetAllPagesFunction-LineItems defined as:

let
    GetAllPages = (PageValue as text, AccumulatedData as table) =>
    let
        CurrentPageData = #"GetPageFunction-LineItems"(PageValue),
        NextPageValue = if Table.IsEmpty(CurrentPageData) then null else CurrentPageData{0}[paging.next.after],
        NewAccumulatedData = Table.Combine({AccumulatedData, CurrentPageData}),
        Result = if NextPageValue = null then NewAccumulatedData else @GetAllPages(NextPageValue, NewAccumulatedData)
    in
        Result
in
    GetAllPages

This function tests if anything is in the current result of “GetPageFunction_LineItems(PageValue)”. If there is it append it to a temporary table called AccumulatedData, and runs the function again with the next PageValue (“paging.next.after” in the code). GetPageFunction-LineItems is defined below:

let
    GetPage = (PageValue as text) =>
    let
        Source = Json.Document(Web.Contents("https://api.hubapi.com/crm/v3/objects/line_items?properties=quartz_course_code&properties=name&properties=hs_recurring_billing_start_date&properties=quantity&properties=amount&limit=100", [Query=[after=PageValue], Headers=[Authorization="Bearer YOUR_HUBSPOT_TOKEN"]] )),
        #"Converted to Table" = Table.FromRecords({Source}),
        #"Expanded results" = Table.ExpandListColumn(#"Converted to Table", "results"),
        #"Expanded results1" = Table.ExpandRecordColumn(#"Expanded results", "results", {"id", "properties", "createdAt", "updatedAt", "archived"}, {"results.id", "results.properties", "results.createdAt", "results.updatedAt", "results.archived"}),
        #"Expanded results.properties" = Table.ExpandRecordColumn(#"Expanded results1", "results.properties", {"amount", "createdate", "hs_lastmodifieddate", "hs_object_id", "hs_product_id", "quantity","name","quartz_course_code", "hs_recurring_billing_start_date"}, {"results.properties.amount", "results.properties.createdate", "results.properties.hs_lastmodifieddate", "results.properties.hs_object_id", "results.properties.hs_product_id", "results.properties.quantity", "results.properties.name", "results.properties.quartz_course_code", "results.properties.hs_recurring_billing_start_date"}),
        #"Expanded paging" = if Table.HasColumns(#"Expanded results.properties", "paging") then Table.ExpandRecordColumn(#"Expanded results.properties", "paging", {"next"}, {"paging.next"}) else Table.AddColumn(#"Expanded results.properties","paging.next.after",each null),
        #"Expanded paging.next" = if Table.HasColumns(#"Expanded paging", "paging.next") then Table.ExpandRecordColumn(#"Expanded paging", "paging.next", {"after", "link"}, {"paging.next.after", "paging.next.link"}) else #"Expanded paging",
        #"Changed Type" = Table.TransformColumnTypes(#"Expanded paging.next", {{"results.id", Int64.Type}, {"results.properties.amount", Int64.Type}, {"results.properties.createdate", type datetime}, {"results.properties.hs_lastmodifieddate", type datetime}, {"results.properties.hs_object_id", Int64.Type}, {"results.properties.hs_product_id", Int64.Type}, {"results.properties.quantity", Int64.Type}, {"results.createdAt", type datetime}, {"results.updatedAt", type datetime}, {"results.archived", type logical}}), //Table.TransformColumnTypes(#"Expanded paging.next", {{"results.id", Int64.Type}, {"results.properties.amount", Int64.Type}, {"results.properties.createdate", type datetime}, {"results.properties.hs_lastmodifieddate", type datetime}, {"results.properties.hs_object_id", Int64.Type}, {"results.properties.hs_product_id", Int64.Type}, {"results.properties.quantity", Int64.Type}, {"results.createdAt", type datetime}, {"results.updatedAt", type datetime}, {"results.archived", type logical}, {"paging.next.after", Int64.Type}, {"paging.next.link", type text}}),
        #"Added Custom2" = Table.AddColumn(#"Changed Type", "getDealIDCustom", each 
            let
                InnerSource = Json.Document(Web.Contents("https://api.hubapi.com/crm/v4/objects/line_items/", [RelativePath=Number.ToText([results.id]) & "/associations/deals", Headers=[Authorization="YOUR_HUBSPOT_TOKEN"]])),
                #"Inner Converted to Table" = Table.FromRecords({InnerSource}),
                #"Inner Expanded results" = Table.ExpandListColumn(#"Inner Converted to Table", "results"),
                #"Inner Changed Type" = Table.TransformColumnTypes(#"Inner Expanded results", {{"results", type any}}),
                #"Inner Expanded results1" = Table.ExpandRecordColumn(#"Inner Changed Type", "results", {"toObjectId", "associationTypes"}, {"results.toObjectId", "results.associationTypes"}),
                #"Inner Expanded results.associationTypes" = Table.ExpandListColumn(#"Inner Expanded results1", "results.associationTypes"),
                #"Inner Expanded results.associationTypes1" = Table.ExpandRecordColumn(#"Inner Expanded results.associationTypes", "results.associationTypes", {"category", "typeId", "label"}, {"results.associationTypes.category", "results.associationTypes.typeId", "results.associationTypes.label"}), 
                InnerGetOneVal = List.First(#"Inner Expanded results.associationTypes1"[results.toObjectId])
            in
                InnerGetOneVal
        ),
        Data = #"Added Custom2"
    in
        Data
in
    GetPage

]]>
https://damienpdevaney.co.uk/hubspot-deals-and-line-items-in-power-bi/feed/ 0
Connecting to Google Maps API from an Excel function https://damienpdevaney.co.uk/the-role-of-it-in-modern-education/ https://damienpdevaney.co.uk/the-role-of-it-in-modern-education/#respond Fri, 28 Feb 2025 21:32:16 +0000 https://damienpdevaney.co.uk/the-role-of-it-in-modern-education/ I have two postcodes in two columns in Excel. I want the distance between the two in a third column.

There are a couple of approaches. One could try and get the longitude and latitude from the postcodes and then calculate an as-the-crow-flies distance mathematically. You could try pasting them into co-pilot and asking it to make an estimation (this would probably work but I had many thousands of rows and co-pilot doesn’t tend to like that).

Fortunately the Google Maps Distance Matrix API offers a method which can take two postcodes and return a distance between them.

Get a Google Maps API developer account (if you don’t have one).

You will need to enter credit card details for this but Google will allow 10,000 API calls per month before charging. This gives you access to Google Maps APIs including the Distance Matrix https://console.cloud.google.com/apis/library/

Google gives an example API call in the form of a url like this:

https://maps.googleapis.com/maps/api/distancematrix/json?destinations=New%20Yok%20City%2C%20NY&origins=Washington%2C%20DC&units=imperial&key=YOUR_API_KEY

Pop this into the address bar of a browser and you can see the raw XML response of the API call:

{
   "destination_addresses" : 
   [
      "New York, NY, USA"
   ],
   "origin_addresses" : 
   [
      "Washington, DC, USA"
   ],
   "rows" : 
   [
      {
         "elements" : 
         [
            {
               "distance" : 
               {
                  "text" : "228 mi",
                  "value" : 367303
               },
               "duration" : 
               {
                  "text" : "3 hours 49 mins",
                  "value" : 13712
               },
               "status" : "OK"
            }
         ]
      }
   ],
   "status" : "OK"
}

Once you have it working, the next step is to get Excel calling this as part of a function.

Create a UDF (User Defined Function) in Excel that calls the Google Maps API

The function will have 2 arguments, Origin and Destination. I am using UK postcodes but the Origin and Destination could be any string that Google Maps will recognise (cities, places of interest etc).

Function G_DISTANCE(Origin As String, Destination As String) As String
Dim myRequest As XMLHTTP60
Dim myDomDoc As DOMDocument60
Dim distanceNode As IXMLDOMNode
G_DISTANCE = 0

Origin = Replace(Origin, " ", "%20")
Destination = Replace(Destination, " ", "%20")
Set myRequest = New XMLHTTP60
myRequest.Open "GET", "https://maps.googleapis.com/maps/api/distancematrix/json?destinations=" _
        & Destination & "&origins=" & Origin & "&units=metric&key=YOUR_API_KEY", False
myRequest.send
    
Dim response As String
response = myRequest.responseText
Dim response1Line As String
response1Line = Replace(Replace(response, vbLf, ""), "  ", "")
Dim distanceStr As String
distanceStr = Mid(response1Line, InStr(response1Line, "distance") + 22, 6)

G_DISTANCE = distanceStr

End Function

The above function takes Google’s XML response and turns into a single string. Then it identifies the part we were looking for (the bit after the word “distance”).

Invoke the function in Excel

You can now use the function in Excel to get the distance between 2 postcodes in cells A2 and B2 like so:

=G_Distance(A2,B2)

]]>
https://damienpdevaney.co.uk/the-role-of-it-in-modern-education/feed/ 0
Updating Hubspot from Power Automate https://damienpdevaney.co.uk/updating-hubspot-from-power-automate/ https://damienpdevaney.co.uk/updating-hubspot-from-power-automate/#respond Thu, 01 Feb 2024 21:36:09 +0000 https://damienpdevaney.co.uk/?p=283 The connector from Power Automate to Hubspot is not reliable in my experience. Fortunately Power Automate allows us to build a HTTP request from scratch.

To do this you need a Hubspot API key.

In Power Automate choose the HTTP step:

This is how to set it up for a POST request to add a contact to Hubspot:

Note you have to write “Bearer ” and then the API key.

Note also that attributes in the body are lower case.

]]>
https://damienpdevaney.co.uk/updating-hubspot-from-power-automate/feed/ 0
Using emails and Power Automate as a simple API https://damienpdevaney.co.uk/using-emails-and-power-automate-as-a-simple-api/ https://damienpdevaney.co.uk/using-emails-and-power-automate-as-a-simple-api/#respond Thu, 11 Jan 2024 21:04:12 +0000 https://damienpdevaney.co.uk/?p=276 Sometimes you might have a website or service without an API but which has some sort of emailing facility. For example, an online form that sends a standard email response.

With Power Automate you can extract whatever data you wish from that email and do what you like with it (update a database for instance).

For example if we have an email like this:

Subject: Form Submission
Body: 
First name - Mickey
Second name - Mouse
...

Power automate can be programmed to search for the text between “First name – ” and “Second name” and (in this case) return “Mickey”.

As the trigger choose “When a new email arrives in a shared mailbox V2” with the Subject Filter “Form Submission”.

Next “HTML” to text:

Next and for several subsequent steps choose the Data Operation step:

Using string manipulation functions like index, length, left, right, mid etc. extract the required data from the body if the email.

Finally, for each item of data extracted, create an Initialize Variable step, setting the value as the output from an earlier Data Operation step.

]]>
https://damienpdevaney.co.uk/using-emails-and-power-automate-as-a-simple-api/feed/ 0