API 2 using Power Query

This is a basic connector to connect Excel (or PowerBI) to Manager api2.

let
    _GetFieldNames = (list) => let
        allRecords = List.Select(list, each _ is record),
        isEmpty = List.IsEmpty(List.Distinct(allRecords)),
        allFieldNames = List.Union(List.Transform(allRecords, each Record.FieldNames(_))),
        uniqueFieldNames = List.Distinct(allFieldNames)
    in
        if isEmpty then {} else uniqueFieldNames,
    
    _ExpandCols = (#"Table to Expand" as table, optional #"Column Name" as text) => let
        tbl = #"Table to Expand",
        colName = #"Column Name",
        allCols = Table.ColumnNames(tbl),    
        colCount = Table.ColumnCount(tbl),
        thisColIdx = if (colName = null) then 0 else List.PositionOf(allCols, colName),
        thisColName = allCols{thisColIdx},
        thisColContent = Table.Column(tbl, thisColName),
        thisFieldList = _GetFieldNames(thisColContent),
        isExpandable = not List.IsEmpty(thisFieldList),
        colNames = if isExpandable then List.Transform(thisFieldList, each thisColName & "." & _) else {},
        newTable = if isExpandable then Table.ExpandRecordColumn(tbl, thisColName, thisFieldList, colNames) else tbl,
        nextColIdx = thisColIdx + 1,
        nextColName = allCols{nextColIdx}
    in
        if nextColIdx > colCount - 1 then newTable else @_ExpandCols(newTable, nextColName),

    _KebabToCamel = (string as text) => let
        camelCaseString = Text.Replace(
            Text.ReplaceRange(
                Text.Proper(
                    Text.Replace(string,"-"," ")
                ),
            0,1,Text.Lower(Text.At(string,0))
        )," ","")
    in
        camelCaseString,

    _SingularKebab = (string as text) => let
        singularString = Text.Replace(Text.Start(string,Text.Length(string)-1),"-and-","-or-")
    in
        singularString,
    
    _ManagerApi2_Connect = (#"Source Path" as text, #"Request Headers" as record) => let
        result = Json.Document(Web.Contents(#"Source Path" , [Headers = #"Request Headers"]))
    in
        result,

    _ManagerApi2_GetRecords = (
        #"Manager Server Address" as text, 
        #"Access Token Secret" as text,
        Route as text, 
        #"Expand Object" as logical,
        #"Page Size" as nullable number,
        #"Field List" as nullable text
    ) => let
    
        source = #"Manager Server Address" & "/" & Route,
        headers = [
            #"accept" = "*/*",
            #"content-type" = "application/json",
            #"x-api-key" = #"Access Token Secret"
        ],
        singularKebab = _SingularKebab(Route),
        pathSummary = _ManagerApi2_Connect(source, headers),
        recordField = _KebabToCamel(Route),
        pageSize = if #"Page Size" = 0 or #"Page Size" = null then pathSummary[totalRecords] else #"Page Size",
        params1 = Record.AddField([], "pageSize", Text.From(pageSize)),
        params2 = try Record.AddField(params1, "fields", Text.Split(#"Field List",",")) otherwise params1, 
        tabView = Table.FromRecords(
            Record.Field(Json.Document(Web.Contents(source, [Headers = headers, Query = params2])), recordField)
        ),
        result = try 
            _ExpandCols(_ExpandCols(Table.AddColumn(tabView,Text.Proper(Text.Replace(singularKebab,"-"," ")), 
                each Json.Document(Web.Contents(#"Manager Server Address" & "/" & singularKebab & "-form/" & [key], [Headers = headers]))
            ))) otherwise tabView
       
    in
        if #"Expand Object" = true then result else tabView
        
in
    _ManagerApi2_GetRecords

Make sure that the authentication for this source is set to “Anonymous”.

It’s quite basic, but it gives the user a starting point where he/she can use Power Query Navigation to arrive at his destination.

I was planning to release a full connector function with dynamic routes, however, I’m facing an authorization issue with Power Query, since logging in to “/api2” requires basic credentials, while accessing anything beyond that requires the x-api-key.

Power Query cannot handle that gracefully and it will cause it to prompt the user to set credentials to anonymous everytime the route changes. This results in an annoying user experience, so I would leave the full connector until I can sort this issue.

For now, this should do just fine and everyone is welcome to contribute to this connector!

Enjoy :slightly_smiling_face:

6 Likes

Update 1: Added _ManagerApi2_Records_Get_All function to automatically get all records. This saves the user some navigation steps.

6 Likes

@Ealfardan Very much appreciate sharing your knowledge here.

1 Like

Thanks for the connector.
I managed to pull receipts and payments using: …/api2/receipts and …/api2/payments, respectively.
However when trying http://127.0.0.1:55667/api2/bank-and-cash-accounts I get:

Any idea of the issue and/or has the connector been updated in the last year?

Welcome to the forum @odiseo123,

I’m aware of this, in fact, I already have made an updated connector which I will post it shortly.

1 Like

Power query connector was updated:

The following issues were solved:

  • Simplified the options to retrieve the object, instead of changing the output function from the code editor, now you can simply set Expand Object to true.

  • Automatically get field path and field names from the Route argument. This solves the issue of having to type in inventory-items then inventoryItems and then inventory-item/[key]. Now all of this is being handled by the connector

  • Added fields selection. This should be entered as a coma separated list, e.g. ItemCode, ItemName. Leave blank for default field list.

    To learn more on the available fields, you can use this connector, which will give you a navigable documents revealing all paths and fields:

let
    Source = Json.Document(Web.Contents("https://{your subdomain}.manager.io/api2"))
in
    Source
  • Added pageSize. In order to retrieve all records, you can set this to 0 or null. Be warned though that when you try set your pageSize to anything other than 50, you must make a fields selection

  • Added the ability to retrieve the objects, expand it and expand custom field object. Manual expansion is no longer required.

1 Like

Thanks.
No luck though:

I would venture it is due to the fact of how the URL is built.
The connector endpoint seems to be formed by appending “-form” but the URL in manager uses “or” instead of “and”:
http://127.0.0.1:55667/bank-or-cash-account-form?

This error occurs when setting “Expand Object” to true. A valid table is returned when set to false.
Setting the call to “true” works as intended with the “payments” and “receipts” tables, correctly breaking up columns into Payment.Date, Payment.PaidFrom, Payment.Description.
It does not work with tables “bank-and-cash-accounts”, “chart-of-accounts”, “investments”.

Do you have any custom fields set on those objects that fail to expand?

This just clicked with me. The singular form of bank-AND-cash-accounts is actually bank-OR-cash-account.

Now it works for bank-and-cash-accounts.

As far as investments, it’s working fine with me, so please share the error you are getting for that.

Finally, As far as “chart-of-accounts”, it is nothing like other tabs. Meaning, it is a collection of different object types like profit-and-loss-statement-account-form and balance-sheet-account-form including different Control Accounts of all sorts and that’s why this isn’t expandable.

However, I still don’t believe that a wrong #“Expand Object” parameter value should result in a catastrophic failure. I made sure in the latest version that this error is handled silently.

Thanks @Ealfardan
bank-and-cash-accounts and investments are working for me. You are probably right that investments was working all along and it was just a copy/paste error on my last message.

I understand the challenge with chart-of-accounts. In the current version I get a flat list of all accounts and their keys. Is there then another table I can pull that captures the hierarchy, namely that X and Y account are children of Z?

If you prefer me to create a separate thread for this question, let me know and I will edit and tag you there.

Thanks for your help.

It looks like you’re using this connector to generate new transaction entries. I will base my answer on this assumption.

I personally never thought of replicating the COA for this particular purpose – or any other purpose really – because I didn’t have to.

My go to solution is to query each individual type of Control Account separately.

Another solution is to use a Custom Control account for every sub account and this will appear inside of its object.

Yet another solution is to affix an identifier to the names or codes of Control Accounts, this way you will have this information on your Chart of Accounts

thanks for the answer and the code but I’m struggling whit some parameters
please look at the screen shot
thanks in advance

Roger

To learn about the routes, you can add this query:

As far as Expand Object, it’s exactly how it sounds like – Sorry but my words betray me :sweat_smile:

You can set it to true and false to test what it does.

I have updated the _ExpandCols functions to workaround a PQ hiccup where PQ doesn’t retrieve the full list of fields and instead uses the lazy method of looking into the first record field–which may or may not be complete.

To help get the full list, a new function _GetFieldNames was added.

1 Like

There is still no way to retrieve custom fields or am I missing something?

You can only retreive custom field by setting Expand Object to true which is effectively retrieving objects one-by-one.

This could be useful for master records like Customers for example, but this isn’t recommended for transactional data like Invoices and Receipts.

1 Like

I am rather a newbee with power query. So I have copied and pasted the above code from Ealfardan. and I have got my endpoint and secret copied and I have put in the Expand object = True, and page size 50? and nothing in field list because I want all but I dont know what to put in Route. All I want is all my customers data. Oh and I have it on anonymous

Hello @Wornout,

Your path should be customers

You can find all available paths by navigating this query:

And about this:

you should leave out the page size if you want ALL customers.

Options

  • Create a backup in Manager of each business
  • Use Copy to clipboard and paste into a spreadsheet for each data type of interest in each business
  • Use Batch update and paste into a spreadsheet for fore each data type of interest in each business
  • Use the API to extract the data of interest. You will need to employ a computer programmer to do this (unless you have recently become a computer programmer and book your time for free)

Assuming you have legal rights to this data, you will need to employ a programmer to do this.

A full copy of Managers data file is virtually all confidential data. To de identify it each piece of data extracted must be aggregated over a sufficient number of business, with sufficient limited aggregate measurement to ensure the data can not be reconstituted by correlation with data from other external sources.

Complying with any requirements to de-identiy accounting records is not at all a trivial task.