Deductions

Along with standard payroll withholdings such as income tax and social security, some employees may need additional deductions withheld from their checks, such as 401K contributions or garnishments. Accounting for these deductions with Zeal is a 2-step process:

  1. Create a Deduction Template.
  2. Create a Deduction (scoped to a single Employee Check).

Understanding Deduction Template Definitions

Before we create a Deduction Template we should first get the Deduction Template Definition Object for the type of deduction (401K, HSA, etc.) we want to address. This object is presented as a JSON schema and defines the instructions, or possible options available, when creating a Deduction Template. This JSON schema can be a little complicated so let's break it down into pieces.

// Full HSA Deduction Template Definition Object

{
   "type": "object",
   "required": [
       "employee_contribution",
       "additional_fields"
   ],
   "properties": {
       "required_template_fields": {
           "const": [
               "employee_contribution"
           ]
       },
       "custom_name": {
           "type": "string"
       },
       "deduction_type": {
           "const": "hsa"
       },
       "employee_contribution": {
           "type": "object",
           "properties": {
               "contribution_type": {
                   "enum": [
                       "dollars"
                   ]
               },
               "value": {
                   "type": "number"
               },
               "override_type": {
                   "enum": [
                       "overridable",
                       "needs_input",
                       "final"
                   ]
               },
               "required_template_fields": {
                   "const": [
                       "value"
                   ]
               }
           },
           "allOf": [
               {
                   "if": {
                       "properties": {
                           "override_type": {
                               "const": "final"
                           }
                       },
                       "required": [
                           "override_type"
                       ]
                   },
                   "then": {
                       "required": [
                           "value"
                       ]
                   }
               },
               {
                   "if": {
                       "properties": {
                           "override_type": {
                               "const": "overridable"
                           }
                       },
                       "required": [
                           "override_type"
                       ]
                   },
                   "then": {
                       "required": [
                           "value"
                       ]
                   }
               }
           ],
           "required": [
               "override_type",
               "contribution_type"
           ]
       },
       "employer_contribution": {
           "type": "object",
           "properties": {
               "contribution_type": {
                   "enum": [
                       "dollars"
                   ]
               },
               "value": {
                   "type": "number"
               },
               "override_type": {
                   "enum": [
                       "overridable",
                       "needs_input",
                       "final"
                   ]
               },
               "required_template_fields": {
                   "const": [
                       "value"
                   ]
               }
           },
           "allOf": [
              {
                  "if": {
                       "properties": {
                           "override_type": {
                               "const": "final"
                           }
                       },
                       "required": [
                           "override_type"
                       ]
                   },
                   "then": {
                       "required": [
                           "value"
                       ]
                   }
               },
               {
                   "if": {
                       "properties": {
                           "override_type": {
                              "const": "overridable"
                           }
                       },
                       "required": [
                           "override_type"
                       ]
                   },
                   "then": {
                       "required": [
                          "value"
                       ]
                   }
               }
           ],
           "required": [
               "override_type",
               "contribution_type"
           ]
       },
       "additional_fields": {
           "type": "object",
           "properties": {
               "hsa_type": {
                   "enum": [
                       "family",
                       "individual"
                   ]
               }
           },
           "required": [
               "hsa_type"
          ]
      }
   }
}

Properties

The first thing to note is the properties field.

// Snippet Showing Properties

// inner details of objects have been omitted for brevity
{
   "properties": {
       "required_template_fields": {
       },
       "custom_name": {
       },
       "deduction_type": {
       },
       "employee_contribution": {
           "properties": {
               "contribution_type": {
               },
               "value": {
               },
               "override_type": {
               },
               "required_template_fields": {
               }
           },
       "employer_contribution": {
           "properties": {
               "contribution_type": {
               },
               "value": {
               },
               "override_type": {
               },
               "required_template_fields": {
               }
           },
       },
       "additional_fields": {
           "properties": {
               "hsa_type": {
               }
           },
      }
   }
}

With the exception of required_template_fields (more on this later), all keys of a properties field directly translate to fields that may be included in the body of your POST request to the Create a Deduction Template endpoint.

// Example Request Body to Create a HSA Template

{
    "companyID": "{{companyID}}",
    "deduction_type": "hsa",
    "custom_name": "Test HSA",
    "employee_contribution": {
        "contribution_type": "dollars",
        "override_type": "needs_input"
    },
    "employer_contribution": {
        "contribution_type": "dollars",
        "value": 0,
        "override_type": "overridable"
    },
    "additional_fields": {
        "hsa_type": "individual"
    }
}

Property Values

The Deduction Template Definition also tells us the values we can assign to the fields of each property. There are a few different types of values these fields might hold so let's go through them.

  • const - field is restricted the value listed.
  • enum - field is restricted to one of the values listed.
  • type - field is restricted to the type listed (ex. "number"-> 5).
// Snippet Showing Property Values

{
   "properties": {
       "deduction_type": {
           "const": "hsa"
       },
       "employee_contribution": {
           "type": "object",
           "properties": {
               "contribution_type": {
                   "enum": [
                       "dollars"
                   ]
               },
               "value": {
                   "type": "number"
               },
               "override_type": {
                   "enum": [
                       "overridable",
                       "needs_input",
                       "final"
                   ]
               }
           }
       }
   }
}

💬

Note

While most values are self-explanatory, the values of the override_type property may be unfamiliar. Please see our API Reference for details on these values.

Required Fields

In example request body above, we included all of the property options that were listed in the HSA template definition. However, not all of the properties are required. The required fields tell us what properties must be included in our request.

// Snippet Showing Required Field

{
    "required": [
       "employee_contribution",
       "additional_fields"
   ],
}

With this in mind, another valid request body to create an HSA template could be as follows since employer_contribution is not a required field.

// Example Request Body to Create a HSA Template

{
    "companyID": "{{companyID}}",
    "deduction_type": "hsa",
    "custom_name": "Test HSA",
    "employee_contribution": {
        "contribution_type": "dollars",
        "override_type": "needs_input"
    },
    "additional_fields": {
        "hsa_type": "individual"
    }
}

📝

Note

The fields companyID, deduction_type, and custom_name are always required.

Conditionally Required Fields

One part of the schema that may not be immediately understood is the allOf fields.

// Snippet Showing allOf field

{
   "properties": {
       "employee_contribution": {
           "allOf": [
               {
                   "if": {
                       "properties": {
                           "override_type": {
                               "const": "final"
                           }
                       },
                       "required": [
                           "override_type"
                       ]
                   },
                   "then": {
                       "required": [
                           "value"
                       ]
                   }
               },
               {
                   "if": {
                       "properties": {
                           "override_type": {
                               "const": "overridable"
                           }
                       },
                       "required": [
                           "override_type"
                       ]
                   },
                   "then": {
                       "required": [
                           "value"
                       ]
                   }
               }
           ]
       }
   }
}

These fields contains an array of condition objects that define when properties may be required depending on the value of another field.

// Snippet Showing a Conditional Object within allOf array

{
  "if": {
    "properties": {
      "override_type": {
        "const": "final"
      }
    },
    "required": [
      "override_type"
    ]
  },
  "then": {
    "required": [
      "value"
    ]
  }
}

For example the snippet above should be understood as "for the employee_contribution object, if the override_type is set to final then the property value will also be required.

With this in mind, it would be valid to create a HSA Deduction Template as follows:

// Example Request Body to Create a HSA Template

{
    "companyID": "{{companyID}}",
    "deduction_type": "hsa",
    "custom_name": "Test HSA",
    "employee_contribution": {
        "contribution_type": "dollars",
        "override_type": "needs_input"
    },
    "additional_fields": {
        "hsa_type": "individual"
    }
}

However, the following example would be invalid:

// Example Of Invalid HSA Request Body

{
    "companyID": "{{companyID}}",
    "deduction_type": "hsa",
    "custom_name": "Test HSA",
    "employee_contribution": {
        "contribution_type": "dollars",
        "override_type": "final" // if "final" then "value" is required
        // missing "value" field here
    },
    "additional_fields": {
        "hsa_type": "individual"
    }
}

Attempting the request above will produce this error:

// Example Of Error Produced by Invalid Request

{
    "success": false,
    "errors": [
        {
            "message": "Invalid deduction template. Ensure deduction template matches template definition",
            "code": 13
        }
    ]
}

Required Template Fields

With everything we've learned so far, we're ready to create Deduction Templates. But you may be thinking, "Hold on. What about this required_template_fields?". Great question!

required_template_fields aren't actually included when creating a Deduction Template. Rather these fields tell us what fields will be required in the subsequent step to create a Deduction using the template.

For example, our HSA Template Definitions state these required_template_fields:

// Snippet Showing Required Template Fields

{
   "properties": {
       "required_template_fields": {
           "const": [
               "employee_contribution"
           ]
       },
       "employee_contribution": {
               "required_template_fields": {
                   "const": [
                       "value"
                   ]
               }
           },
       }
   }

This means that when we create a Deduction using this Deduction Template, we'll need to include the employee_contribution object with the field value in our deduction object.

// Example Request Body to Create a HSA Deduction

{
    "companyID": "{{companyID}}",
    "deductionTemplateID": "{{deductionTemplateID}}",
    "employeeCheckID": "{{employeeCheckID}}",
    "deduction": {
        "employee_contribution": {
            "value": 50
        }
    }
}

With this, we have a full, thorough understanding of Deduction Template Definitions. Thankfully, this is the most difficult step in the process of creating deductions. Now, we can easily complete the 2-step flow.


Create a Deduction Template

A Deduction Template is an object that defines the schema for a Deduction. Deduction Templates may be reused across a company (Employer) for many employees or may just be reused to create deductions for a single employee.

For example:

  • An employer might create a 401K Deduction Template that defines a fixed employer contribution but allows the employee contribution to be adjusted with each deduction created. This might be reused across multiple employees.
  • An employee has a particular case where many garnishments or miscellaneous need to be withheld from their paycheck. The Deduction Templates that are defined to accommodate this use case might only be used to create deductions for this particular employee.

It's important to understand the scope of a Deduction Template before creating it. Now we'll walk through creating a Deduction Template.

  1. Call Get Deduction Template Definitions with the type of deduction you're targeting as a query parameter. We'll choose 401k for this example.

📝

Note

Remember to replace the placeholders such as {{testApiKey}} in the examples below.

curl --request GET \
     --url 'https://api.zeal.com/deductionTemplateDefinitions?deduction_type=401k' \
     --header 'Accept: application/json' \
     --header 'Authorization: Bearer {{testApiKey}}'
  1. Use the JSON Schema returned as instructions to build your request to create a deduction template.
  2. Call Create Deduction Template.
curl --request POST \
     --url https://api.zeal.com/deductionTemplate \
     --header 'Accept: application/json' \
     --header 'Authorization: Bearer {{testApiKey}}' \
     --header 'Content-Type: application/json' \
     --data '
{
     "employee_contribution": {
          "override_type": "needs_input",
          "contribution_type": "percentage",
          "value": 3
     },
     "employer_contribution": {
          "override_type": "final",
          "contribution_type": "dollars",
          "value": 200
     },
     "companyID": "{{companyID}}",
     "custom_name": "My 401k Template",
     "deduction_type": "401k"
}
'
  1. Store the returned deductionTemplateID for use when creating deductions following this template.

Create a Deduction

A Deduction defines how much should be withheld from an employees pay and is scoped to a single Employee Check. Below are the steps to create a Deduction.

  1. Get the Employee Check you'd like to apply the Deduction to.
curl --request GET \
     --url 'https://api.zeal.com/employeeCheck?companyID={{companyID}}&employeeID={{employeeID}}&status=pending&reportingPeriodID={{reportingPeriodID}}}' \
     --header 'Accept: application/json' \
     --header 'Authorization: Bearer {{testApiKey}}'
  1. Get a list of Deduction Templates and grab the deductionTemplateID for the desired template (or use the ID you stored from the previous steps).
curl --request GET \
     --url 'https://api.zeal.com/deductionTemplate?companyID={{companyID}}' \
     --header 'Accept: application/json' \
     --header 'Authorization: Bearer {{testApiKey}}'
  1. Create the Deduction.
curl --request POST \
     --url https://api.zeal.com/deductions \
     --header 'Accept: application/json' \
     --header 'Authorization: Bearer {{testApiKey}}' \
     --header 'Content-Type: application/json' \
     --data '
{
    "companyID": "{{companyID}}",
    "deductionTemplateID": "{{deductionTemplateID}}",
    "employeeCheckID": "{{employeeCheckID}}",
    "deduction": {
        "employee_contribution": {
            "value": 50
        }
    }
}
'

Congratulations

You've created your first Deduction! Now Zeal's system will pick up the Deduction when the Employee Check is processed. Zeal will properly calculate the taxes depending on the type of Deduction (post-tax or pre-tax), the employee will receive their net pay, and the deduction amount will remain in the employer's bank account (Note: garnishments are the exception. These amounts will be deducted from the employer's bank account and paid out to the proper stakeholders by Zeal).