Karbon API Examples

  • 30 March 2022
  • 9 replies
  • 598 views

Userlevel 7
Badge +19
  • Sr. Karbon Community Guide
  • 1114 replies

Skip Ahead:

Introduction

Play along at GitHub: https://github.com/dchrm/human-accounting-mastermind

There are certain things in Karbon that I cannot automate the way I want, so I found that the API has worked in many cases. I’m curious how you are using the API. What problems are you solving? I’ll be sharing my implementations here and I welcome anyone to join and share theirs as well. I’ll edit this post with your examples so that we can start to build a repository of real-world DIY API implementations.

Examples

Add a template-based work item to a list of clients

Our firm is focused on continuous improvement, so we quickly learned that annual recurring work items carry forward with them the poor layout of the year before. We are continually updating our templates with new learning, but our recurring work was missing out on the new way we were approaching work. We decided to add all of our recurring individual income tax returns as work items from the template this year. Enter the API. A quick way to add many work items automatically.

### Instructions
"""
1. Request access to the Karbon API here: https://developers.karbonhq.com/
2. Install Python from here: https://www.python.org/downloads/
(on Windows 10, I found that it's best to install through the Microsoft store)
The installation should include almost everything you need
3. Install Python Requests (Instructions: https://youtu.be/cGW70Hc0W4M?t=111) The biggest thing is to change directories "CD" to the folder where your PIP is installed
4. I recommend using VS Code with the Python extension for running and troubleshooting your code.
This will give you advanced autocomplete functions that turn the libraries listed below into lookup fields when you are updating your request at the bottom
It will also help you identify what's wrong with your code before you try to run it against the API
5. Add your company's information to the appropriate fields in a config.py file (example file available here: [Link TBD after posting to Karbon Community]).
This is the hardest and most time-consuming part of the project.
The example file has instructions for how to gather your company's information
6. Update the API request parameters at the bottom according to your needs.
7. Run and debug your request.
I suggest using a test client profile... do not run untested code on actual clients
8. For all troubleshooting, I suggest starting with W3 Schools: https://www.w3schools.com/python/default.asp
You may also attempt to use the API docs, but they are missing important details: https://developers.karbonhq.com/api
If something isn't working, hit up the Karbon community: https://community.karbonhq.com/
If you still cannot get your project to work, chatting with Karbon support is always really helpful.

This code was written by Max with help from various Karbon and online sources.
If you have any questions, please contact me on the Karbon Community: https://community.karbonhq.com/members/max-225
"""


### Imports
import requests # Do not change this. If you are getting an error, you likely do not have requests installed.
import json # Do not change this
import config # Importing the Config file to get access to its variables

### Standard Values
keyBearerToken = config.BearerToken # Sets the bearer token from the config file
keyAccessKey = config.AccessKey # Sets the access key from the config file
valBaseURL = "https://api.karbonhq.com/" # Do not change unless there is new API instructions
valAPIVersion = "V3" # Change to the version of the API you would like to use. At the time of this writing, V3 was the only version available

dictTemplates = config.Templates # Adds the templates from the config file

# Dictionaires
Role = {
"Admin: Client": "3WyRRqCJpG2L",
"Staff: New Hire": "28nDXnk7C8Lz",
"Staff: Operations Manager": "2ywBBD78fwkT",
"Staff: Partner": "41G9Y5pb4lzv",
"Tax: Preparer": "375z1yfn9zrq",
"Tax: Reviewer": "4yf8m1wpZpH3",
"Salesperson": "2dtZ435zgQjl",
"Hiring: Screener": "3LRlrlNgZNJG",
"Hiring: Partner": "4gSwgswP68wR",
"Hiring: Manager": "2BBhtkbJPSpx",
"Bookkeeping: Preparer": "T6J5Qt3QFmN",
"Bookkeeping: Reviewer": "33zpd4S78Sj4",
"Staff: Manager": "T5wvpqcpCLH"
}

UsersEmail = {
# Redacted
}

# Profile keys can be found at Settings > Colleagues ... click on each and get the key from the URL
UsersProfileKey = {
# redacted
}

# Assgin client types ... These are fixed. DO NOT CHANGE
dictClientType = {
"Organization": "Organization",
"Contact": "Contact",
"ClientGroup": "ClientGroup"
}

### List of client Keys
# Each client/key will get the work item setup below added to their work list
listClients = [
# ["3jD7PYGwJ2Jf","ClientGroup"] # a test client
]

### Request ### This is the actual API request

for thisClient in listClients:

url = valBaseURL + valAPIVersion + "/WorkItems" # This builds the API URL

payload = json.dumps({
"ClientKey": thisClient[0],
"ClientType": thisClient[1], # Only update what is between the last set of quotation marks. Make sure this matches the kind of clients to whom you are adding work. Organizations, Contacts, and Client Groups are treated differently.
"AssigneeEmailAddress": UsersEmail["Admin"], # Only update what's between the end set of quotation marks. Enter the name of the person to whom the work will be assigned.
"StartDate": "2022-01-01", # Enter your desired start date
"DueDate": "2022-04-15", # Enter your desired due date
"Title": "2021 Individual Income Tax Return", # Enter your desired due date
"Description": "", # Enter a description of your choice. Look up how to add line breaks and other formatting at W3 Schools
"WorkTemplateKey": "ZJQMrm2NPj6", # Choose the template you would like to apply to your work item.
"WorkStatus": "Ready to Start", # I recommend not using this and letting your template set the work status instead
# "WorkScheduleKey": "", # I have so far been unable to get this to do anything. There's a way to add a work schedule to an existing work item, but adding a schedule key here seems to have no effect.
# "EstimatedBudget": , # From what I understand, estimated budgets have been depreciated from the API. The documentation hasn't caught up and adding this to the payload seems to have no effect.
"UserRoleAssignments": [ # Add a role section for each role in your tempalte. Preserve the structure. You can have 0 to as many role assignments as you like.
{
"RoleKey": Role["Admin: Client"],
"UserProfileKey": UsersProfileKey["Admin"]
},
{
"RoleKey": Role["Staff: Partner"],
"UserProfileKey": UsersProfileKey["Partner"]
},
{
"RoleKey": Role["Tax: Reviewer"],
"UserProfileKey": UsersProfileKey["Reviewer"]
},
{
"RoleKey": Role["Staff: Manager"],
"UserProfileKey": UsersProfileKey["Manager"]
}
]
})

headers = {
'Content-Type': 'application/json', # Do not change. This tells Karbon how we are sending our request and how we would like the information returned
'Authorization': 'Bearer ' + keyBearerToken, # Do not change. This is how Karbon tells how is trying to use the API
'AccessKey': keyAccessKey # Do not change. This is how Karbon knows that you have permission to use the API
}

response = requests.request("POST", url, headers=headers, data=payload) # This accepts the response from Karbon which will be a success or error message

json_object = json.loads(response.text) # This picks up the text from Karbon and makes it a JSON object
json_formatted_str = json.dumps(json_object, indent=2) # This formats the JSON so that it's readable

print(json_formatted_str) # This outputs the response from Karbon to the console for review. It's also the last step in our "For" loop.

 


​​​​​​​Basic tools for running API requests (Python)

I built a little function that tries to lay the groundwork for more complicated procedures.

Check out GitHub as it’ll be updated more often than this community page. 😀

Also, I have a learning disability. Don’t mind the typos. If you care, submit pull requests in GitHub. 😉

"""
This project aims to build a tempalte to access pageanated api responses from karbon
"""

import requests
import json
import config

# add funcitons here to use inside the basic_api_requests() function
def add_records_to_sql_database(array_of_rows,database,table,row_titles):

# check if list of rows is over the insert limit
if len(array_of_rows) > 1000:

# split array into chunks of 1000


# for each array chunk, insert the rows to the database


pass

# this function makes all the necessary requests to get a full list of vlaues from paganated api responses
def basic_api_requests (request_perameters):

# define heads for the api request
headers = {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + config.BearerToken,
'AccessKey': config.AccessKey
}

# make the qpi reqeust
response = requests.request("GET", config.base_url + request_perameters, headers=headers)

# convert response to json
json_object = json.loads(response.text)

# extracts the value array
rows = json_object["value"]

# add any functions you want to use with your data here


# when a next link exists, call it
if "@odata.nextLink" in json_object:

# since the next link provided by karbon doesn't work, this peels off the new parameters
next_parameters = json_object["@odata.nextLink"].split("V3/",1)

# this calls this same function again to get the rows from the next group
next_rows = basic_api_requests(next_parameters[1])

# combine this functions rows with the rows from the next link
rows.extend(next_rows)

# returns the rows from the value section of the api call
return rows

## this section will hold the code you want to actually execute
# put your query parameters in the text section here
api_reqeust_parameters = 'Timesheets?$filter=StartDate gt 2021-12-31&$expand=TimeEntries'

# put your code here -- what do you wnat to do?
basic_api_requests(api_reqeust_parameters)

 

 


9 replies

@max  thanks for sharing these!

Userlevel 7
Badge +19

I plan to fill this out much more. 😀 It’s a little sparse right now, lol.

Userlevel 4
Badge +5

Oh my!  I’m going to have to put my learning cap on!  Excited!

Userlevel 7
Badge +19

I added a link to the GitHub repository and a code example with a function that will pull all values from a paginated api response.

Userlevel 7
Badge +19

I updated GitHub with a file containing four functions to get information and add it to a database.

I’m these functions to refresh my timesheet and time entry data once a day starting in January 2022. It presented some problems:

  • Because I’m not good at this, lol.
  • The GET request returns paginated responses
    • Must call the API several times to get all the data
    • The API response provides a URL “nextLink” to pull the next page if it needs it
      • The nextLink URL from the API doesn’t work when called
      • I split off the parameters and add it to the correct API base URL.
  • There are two ways to handle the data:
    • Gather all the data into one large dataset on which to operate
    • Operate on the data page by page as the function returns new pages of values

I chose to build my example of collecting time data by operating on the data as it came in instead of building a large dataset and then operating on that.

I updated GitHub with the functions and some comments. I’d love to hear from anyone interested in this and improving it.

Userlevel 7
Badge +19

I updated GitHub with a SQL script file you can use to create the entire database with all the correct field  names, data-types, and foreign key constraints. You should be able to copy and paste the script into SQL Server Management Studio.

I did not test the script, so if you run into issues, please drop them here or on GitHub. 😀

The file also doesn’t contain the information to fill a date table. I will add that information later.

Userlevel 2
Badge +1

yeah @max long time no forum, pardon--

 

where did you get the “RoleKey” values in order to assign roles to staff with the workitem creation example, above?

 

thanks!

Userlevel 7
Badge +19

You must pull each key manually for each role from the url when visiting the role settings page in Karbon. No known way to export roles or to pull them programmatically. 

Here’s a video showing how to find it:

 

Userlevel 2
Badge +1

@max you’re a lifesaver

Reply