Solved

WorkItems from API with client tasks that send 0 days after start date


Userlevel 2
Badge +1

I am comfortably creating and updating workitems using the API with some hacky python scripts,

 

but I’m unable to get recipient data into the workitems for templates with client tasks--

 

the API includes a “ClientTaskRecipient” object and I feel comfortable assuming that its “ClientKey” field is the ContactKey and the “EmailAddress” field is the contact’s e-mail

 

don’t know if I’m right; have no clue what to pass into the “LinkType” field and am having difficulty getting a response from support, for it.

 

Perhaps a little niche but I would love whatever insight anyone may have

 

Thanks!

Mike

icon

Best answer by max 10 March 2022, 16:38

View original

29 replies

The link type is the contact type of the recipient. The main values would be 'Contact' and 'Organization'.

 

"ClientTaskRecipient":

{
  "RecipientKey": "",
  "LinkType": "Organization", 
  "EmailAddress": ""},

 

RecipientKey = contact key

LinkType = Organization/Contact

EmailAddress = email address

 

It should be noted that client tasks will send automatically when using this option.

Userlevel 3
Badge +2

Thanks @Rowan. I’ve created a feature request related to this: 

 

Userlevel 3
Badge +2

@Mike Richardson @max just wanted to let you know that the Karbon developers have updated the API and I think the Client Task Recipient for WorkItems is working correctly now. I just did some limited testing on my end and it behaved “as expected”. Please let me know how your testing goes and if you see any other issues with it.

Userlevel 7
Badge +19

There’s a little section paste in code here:

It comes out a little easier to read:

from datetime import date

import json



from dateutil import parser

import requests



baseUrl = 'https://api.karbonhq.com'
items_url = '/v3/WorkItems'



contact_headers = {
'AccessKey': ACCESS,
'Authorization': 'Bearer ' + BEARER,
'content-type' : 'application/json',
}



def test_send_tasks(appt, client):

params = {
"Name": appt['type'],
"Description": 'Time to Organize the Tax',
"UserRoleAssignments": [
{
"Id": STAFF['admin']['id'],
"RoleKey": "",
"UserProfileKey": ""
}
],
"ClientTaskRecipient":
{
"RecipientKey": client['ContactKey'],
"LinkType": "Contact",
"EmailAddress": appt['email']
},

"AssigneeEmailAddress": STAFF['admin']['email'],
"Title": f"{appt['firstName']} {appt['lastName']} - {parser.parse(appt['date']).strftime('%m/%d')}",
"ClientKey": client['ContactKey'],
"ClientType": 'Contact',
"RelatedClientGroupKey": "",
"ClientGroupKey": "",
"StartDate": date.today().strftime('%a %b %d, %Y'),
"DueDate": appt['date'],
"CompletedDate": "",
"ToDoPeriod": "",
"WorkType": "",
"WorkStatus": "Ready to Start",
"WorkTemplateKey": "2W57sm27MYrT",
"WorkScheduleKey": "",
"EstimatedBudget": 0
}
data = json.dumps(params)


return requests.post(baseUrl + items_url, headers=contact_headers, data=data)



appt = {
'type': 'Tax Appointment',
'email': 'somethingthatworks@email.com',
'firstName': 'Mike',
'lastName': 'Richardson',
'date': 'March 15, 2022',
}

client = {
'ContactKey': 'yeah'
}

r = test_send_tasks(appt, client)

I’ll dig into this probably over the weekend.

Userlevel 2
Badge +1

@max yeah I’m genuinely grateful for another set of eyes on this, no matter the time-frame

 

thanks

Userlevel 3
Badge +2

The link type is the contact type of the recipient. The main values would be 'Contact' and 'Organization'.

 

"ClientTaskRecipient":

{
  "RecipientKey": "",
  "LinkType": "Organization", 
  "EmailAddress": ""},

 

RecipientKey = contact key

LinkType = Organization/Contact

EmailAddress = email address

 

It should be noted that client tasks will send automatically when using this option.

Jumping into this space now and learning LOTS. Huge thanks to Max and Mike for sharing some of their work. With their help and my experimentation, I have successfully created work items from a template for all clients within a client group. However, similar to Mike, I have [several] client tasks in my work items. I would love to set up the work items such that only the *first client task automatically sends to the client on the work start date. However, if I pass recipient info (per this thread), it sets *all client tasks to automatically send to the client on the work start date. I see that Rowan suggests this in his post above, but was hoping to get around it [somehow]. Any ideas?

Userlevel 3
Badge +2

Clarification: my work template already had the client tasks within within it. I did not send the client task via the API, was simply trying to assign it to the client.

Userlevel 2
Badge +1

@Smitty @Rowan 

 

I am trying to do the same thing that Smitty described and experiencing possibly buggy behavior--

 

using the API as above, I am creating work items from a template with more than one client tasks section.

 

the first section I do want to auto-send and have adjusted to sending settings in the template to reflect that:

 

the second section I do not want to auto-send:

 

despite this, both client tasks send when I create the work via the API

 

thanks for looking into this!

Mike

Userlevel 2
Badge +1

ok this has been quite a ride

 

I was passing in a “Name” to the json payload that, now removed, has my code functioning perfectly

 

literally just went line by line matching up my code to @max’s example above and found the unnecessary “Name” I was passing in.

 

THANK YOU WOW

Userlevel 2
Badge +1

fabulous

 

let me clean up my code for public consumption and I will absolutely contribute to much-needed code examples

Userlevel 3
Badge +2

Nice! If we can set and send client requests to a recipient, that will be a game changer for this tax season. Programmatic, customized client requests sent automatically… could be huge.

 

Yeah, similar. We set up the first client task to send automatically 1 day after the work item start date. Using the API I plan to set the work item start date to Jan 2 and will also set the recipient of the client tasks. 

 

Really wish we could kick off a client task based on the completion of prior tasks. And also wish we could send out client tasks that didn’t actually have tasks (just an email).

Userlevel 7
Badge +19

@Rowanthis sounds like something you and I talked about.

I have not messed with client task emails in the API, so I’m curious to see how others implement it.

@Mike Richardsonhave you tried pulling the information with a GET request to see what’s populated in the fields of an existing work item that’s been manually setup to be what you want to achieve with the API?

 

[EDIT]

Upon further reading of the docs, it doesn’t seem like there’s a way to GET this kind of information and the dummy information in Postman isn’t helpful.

Userlevel 2
Badge +1

the GET is an excellent idea, thanks @max!

 

I tried two experiments, one before the client tasks had been sent and one after:

 

both returned a json object with None as its ClientTaskRecipient field

 

And per @Rowan’s super helpful answer, I just passed “Contact” into the LinkType field and got two different results depending on how I ran it through the requests library (with/without an ‘application/json’ in the header):

 

a new workItem with no recipient:

 

or a 400:

 

{'error': {'code': '4005', 'message': 'Required payload data is missing: workItem'}}

 

I am a self-taught python developer, I apologize; I am baffled!

Userlevel 7
Badge +19

I’m mostly self-taught as well. :) Though, I have benefited from the W3 schools course.

Are you able to scrub your python of all tokens and other private data and share it here? I’ll test it out myself and play around with it and post back my results. 

I ran into an issue before with contact types where I wasn’t capitalizing them correctly and was getting all kinds of errors. 

Userlevel 2
Badge +1

fabulous @max 

let’s see how this pastes in:

 

from datetime import date

import json

 

from dateutil import parser

import requests

 

baseUrl = 'https://api.karbonhq.com'
items_url = '/v3/WorkItems'

 

contact_headers = {
    'AccessKey': ACCESS, 
    'Authorization': 'Bearer ' + BEARER,
    'content-type' : 'application/json',
}

 

def test_send_tasks(appt, client):

    params = {
        "Name": appt['type'],
        "Description": 'Time to Organize the Tax',
        "UserRoleAssignments": [
            {
            "Id": STAFF['admin']['id'],
            "RoleKey": "",
            "UserProfileKey": ""
            }
        ],
        "ClientTaskRecipient": 
            {
            "RecipientKey": client['ContactKey'],
            "LinkType": "Contact",
            "EmailAddress": appt['email']
            },
        
        "AssigneeEmailAddress": STAFF['admin']['email'],
        "Title": f"{appt['firstName']} {appt['lastName']} - {parser.parse(appt['date']).strftime('%m/%d')}",
        "ClientKey": client['ContactKey'],
        "ClientType": 'Contact',
        "RelatedClientGroupKey": "",
        "ClientGroupKey": "",
        "StartDate": date.today().strftime('%a %b %d, %Y'),
        "DueDate": appt['date'],
        "CompletedDate": "",
        "ToDoPeriod": "",
        "WorkType": "",
        "WorkStatus": "Ready to Start",
        "WorkTemplateKey": "2W57sm27MYrT",
        "WorkScheduleKey": "",
        "EstimatedBudget": 0
        }
    data = json.dumps(params)


    return requests.post(baseUrl + items_url, headers=contact_headers, data=data)

 

appt = {
    'type': 'Tax Appointment',
    'email': 'somethingthatworks@email.com',
    'firstName': 'Mike',
    'lastName': 'Richardson',
    'date': 'March 15, 2022',
}

client = {
    'ContactKey': 'yeah'
}

r = test_send_tasks(appt, client)

 

not bad!  let me know if you have any questions and thanks so much for taking a crack at it

Userlevel 2
Badge +1

@Smitty it totally works!

 

thank you all

Userlevel 7
Badge +19

I didn’t take time this weekend to look at this, but it’s still on my radar. 😀

Userlevel 7
Badge +19

 I would love to see how you do that. I avoided sending client tasks via API, so I’m eager to learn.

Userlevel 3
Badge +2

@Rowan Can you please clarify if there is any way to control the “auto-sending” of client tasks via the API? We’d love to be able to assign all client tasks for a given work item to the client, but do not want them all to auto-send. Is this possible?

When the client task element below is included in the payload it will cause all client tasks in that work item to be sent, even if they are not set to be automatically sent in the work template. There is no way to fill in the recipient without automatically sending currently. 
 

"ClientTaskRecipient": {

  • "RecipientKey": "abc123",

  • "LinkType": "abc123",

  • "EmailAddress": "abc123"

}

This behaviour does limit the scope of use somewhat. I would suggest adding a feature request for any changes that you would like to see to this. 

Userlevel 7
Badge +19

I started looking through this. I’m going through and commenting the code to try to understand it a little better. Do you have a version of this that’s got comments?

Userlevel 2
Badge +1

sigh I do not—

 

I was wondering how you were creating WorkItems for your firm, programmatically 

 

I’ve found a funny thing where the requests library w/out a json payload works for WorkItems ( except in this use case ) but the json is necessary to create a contact with a functioning “business card”

 

still very curious to get another set of eyes on this—

 

should I comment my code?

Userlevel 7
Badge +19

Commenting would help me see what you’re trying to do. LOL, reading my own code is hard enough, but reading someone else’s can be difficult.

I can share my process for adding the Individual Income Tax work items this year, but I don’t think it’s quite what you are looking for. You mentioned that you want to add the client task recipient as well, and that’s something I skipped in my setup.

I used Excel to create my list of client keys and then executed a loop to add the 1040 work item for each client. My staff then went back and sent the emails to the clients manually.

I would love to figure out how to send the emails automatically. That would be nice.

Userlevel 2
Badge +1

you mentioned you’re using python--

 

are you using the requests library for your HTTP posts to create the WorkItems?

Userlevel 7
Badge +19

Here’s my code:

### 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 reccomend 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 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 chagne this. If you are getting an error, you likey do not have requests installed.
import json # Do not change this
import config # Importing the Config file to get access to it's 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 > Collegues ... 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 assgined.
"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 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 reccomend 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 has 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. Perserve 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 consol for review. It's also the last step in our "For" loop.

 

 

Yes, I use the requests library.

Reply