formQuestionSet​/{formId}​/{questionSetId} (GET)

The formQuestionSet endpoint returns a question set for a given form. Each question set may contain one or more questions, validation regular expression, and navigation aids.

We provide form versioning through a query parameter. You can collect all the details of a form and its version from the ​fillPdf/{formId} (GET) endpoint. This is an optional query param and if not included will provide the latest form version. It should be noted that we recommend that a form version is included and will send back a warning with additional information if it is not.

We provide language support for English, Spanish and French.

Displayed below is an example response returned by requesting Question Set 6 ("QS6") for the Federal W4 form (W4101).

{
    "id": "QS6",
    "notes": [
        {
            "htmlType": "PARAGRAPH",
            "suggestedPlacement": "ABOVE_ALL_QUESTIONS",
            "text": "If you choose the option in Step 2(b) on Form W-4, complete this worksheet (which calculates the total extra tax for all jobs) on only ONE Form W-4. Withholding will be most accurate if you complete the worksheet and enter the result on the Form W-4 for the highest paying job."
        },
        {
            "htmlType": "PARAGRAPH",
            "suggestedPlacement": "ABOVE_ALL_QUESTIONS",
            "text": "If more than one job has annual wages of more than $120,000 or there are more than three jobs, see Publication 505 for additional tables."
        },
        {
            "htmlType": "IMAGE",
            "suggestedPlacement": "ABOVE_ALL_QUESTIONS",
            "href": "https://spfcdn-test.symmetry.com/images/W4101/2020.1.0/w4101_single-table.png",
            "optionalLabel": "Page 4 Taxable Wage and Salary Tables"
        }
    ],
    "questions": [
        {
            "id": "multipleJobsWorksheetLine1",
            "questionText": "1. Two jobs. If you have two jobs or you're married filing jointly and you and your spouse each have one  job, find the amount from the appropriate table on page 4. Using the \"Higher Paying Job\" row and the \"Lower Paying Job\" column, find the value at the intersection of the two household salaries and enter that value on line 1. Then, skip to line 3.",
            "validationRegex": "^[+-]?[0-9]{1,3}(?:,?[0-9]{3})*(?:\\.[0-9]{2})?$",
            "validationErrorMessage": "Please enter a valid dollar amount",
            "htmlType": "INPUT",
            "displayType": "DOLLAR",
            "required": {
                "whenRequired": "DEPENDENT"
            },
            "isCalculated": false
        },
        {
            "id": "multipleJobsWorksheetLine3",
            "questionText": "3. Enter the number of pay periods per year for the highest paying job. For example, if that job pays weekly, enter 52; if it pays every other week, enter 26; if it pays monthly, enter 12, etc.",
            "validationRegex": "^[1-9]\\d*|0$",
            "validationErrorMessage": "Value must be a whole number zero or greater",
            "htmlType": "INPUT",
            "displayType": "INTEGER",
            "required": {
                "whenRequired": "DEPENDENT"
            },
            "isCalculated": false
        },
        {
            "id": "multipleJobsWorksheetLine4",
            "questionText": "4. Divide the annual amount on line 1 or 2c by the number of pay periods on line 3. (You may round this  to the closest whole dollar amount.)  Enter this amount here and on line 4c of Form W-4 for the highest paying job.",
            "htmlType": "INPUT",
            "displayType": "DOLLAR",
            "required": {
                "whenRequired": "NEVER"
            },
            "calculation": {
                "formula": "( multipleJobsWorksheetLine3 > 0 ) ? ( multipleJobsWorksheetLine1 / multipleJobsWorksheetLine3 ) : 0",
                "scale": 2
            },
            "isCalculated": true
        }
    ],
    "breadcrumbTitle": "Step 2b",
    "navigation": {
        "navigationType": "CONSTANT",
        "navigationMapping": {
            "nextQuestionSetId": "QS21",
            "hasMoreQuestions": true
        }
    }
}

Outer Objects

  1. id represents which question set you are on.
  2. questions represents an array of question objects to help you filter down to a formsToComplete array.
  3. breadcrumbTitle represents our recommended title for breadcrumbs relating to the question.
  4. navigation represents an object to help navigate through the guided-flows

Questions Objects

  1. id represents the question id for the current question set.
  2. questionText represents the text we recommend you display related to the question.
  3. validationRegex represents the only values that the api will allow in relation to the matching question.
  4. validationErrorMessage represents a recommended error message related to the question.
  5. isCalculated a boolean flag to specify if a question is result of performing a calculation based on response to other questions from the form and are provided for context. Note: when isCalculated is true, validationRegex and validationErrorMessage will not be provided, instead returning a calculation object. Calculated fields, if displayed, should be disabled to prevent users from setting their value, instead the value should be derived using the calculation provided in the calculation object. Calculated questions are not required when submitting question responses to the (doc:fillpdf-post) endpoint and will be ignored if provided.
  6. calculation represents a formula, scaling, and rounding mode to calculate the value for a calculated field. Calculation is only returned if isCalculated is true and will always contain a formula and a scale, with a rounding mode returned when required by the particular form. The formula will contain the question IDs, for which, the question's value should be substituted to process the calculation. Please note, the formula can contain values from a previous answer. If you choose to implement calculated fields, as you collect answers to questions, it will be necessary to compare the previously answered question id to see if it is a value included in the calculation formula.

In the calculation below, we must retrieve the user's response to questions multipleJobsWorksheetLine1 and multipleJobsWorksheetLine3. We can then check if multipleJobsWorksheetLine3 is greater than 0, and finally returning a value of multipleJobsWorksheetLine1 divided by multipleJobsWorksheetLine3 or 0 with a scale of 2 decimal places.

"calculation": {
  "formula": "( multipleJobsWorksheetLine3 > 0 ) ? ( multipleJobsWorksheetLine1 / multipleJobsWorksheetLine3 ) : 0",
  "scale": 2
}

The next calculation is returned with a rounding mode. Again, we must retrieve the user's response to question specified in the formula, worksheetB_line7. Next, we divide worksheetB_line7's value by 1000, and finally, we round the result to a scale of 0 decimal places, i.e. a whole number.

"calculation": {
  "formula": "worksheetB_line7 / 1000",
  "roundingMode": "HALF_UP",
  "scale": 0
}

📘

Supported rounding modes

SPF API supports 3 different rounding modes:

CEILING: Rounding should be performed to the next largest value, i.e. rounding up.
Ex: 1.5 -> 2

FLOOR: Rounding should be performed to the next smallest value, i.e. rounding down.
Ex: 1.5 -> 1

HALF_UP: Rounding should be performed to the nearest value with ties rounding up.
Ex: 1.2 -> 1
1.7 -> 2
1.5 -> 2

  1. htmlType represents our html recommendation for a UI to display. HTML types we use are INPUT, SELECT, TEXTAREA, PARAGRAPH, UNORDERED_LIST, IMAGE, and TABLE.
  2. displayType represents additional information about our html recommendation if required for that html type. Display types we use are TEXT, RADIO, CHECKBOX, MULTI_SELECT_CHECKBOX, MONTH_YEAR, YEAR, TELEPHONE, EMAIL, ZIPCODE, INTEGER, PERCENT, and DOLLAR.
  3. required represents an object whenRequired. This object whenRequired has three enum values (ALWAYS, DEPENDENT, and NEVER). Based upon these values you will know whether a question must be answered and sent back to the API to complete the process.

Field Validation whenRequired is set to NEVER

🚧

Required, Validation Regex, and Validation Error Message

Before moving on it we want to make it clear that the required field is independent from the validation regex and validation error message field.

The required field only pertains to whether a question is required to be submitted to the API. If whenRequired is set to NEVER and no value is submitted then the validationRegex and validationErrorMessage will never be triggered. On the other hand, if a value is submitted, then it will be validated by the validationRegex and if it is an incorrect value we will display the validationErrorMessage.

whenRequired is set to DEPENDENT

🚧

When the whenRequired field is set to DEPENDENT, this means that a question is situationally required based on the answer to a previous question. This is done so that the API can process form questions during pdf generation, requiring DEPENDENT questions only when situationally required. Client applications should treat DEPENDENT questions as required, and validate user input as such.

  1. questionOptions represents an object array containing labels and values. Labels represent the text we recommend is displayed, while values are what we accept to be returned to the API. Radio and select will come with questionOptions.
"questionOptions": [
  {
    "label": "I choose to have Arizona withholding at the rate of 0.8% of my gross taxable wages",
    "value": "0.8"
  },
  {
    "label": "I choose to have Arizona withholding at the rate of 1.3% of my gross taxable wages",
    "value": "1.3"
  },
  {
    "label": "I choose to have Arizona withholding at the rate of 1.8% of my gross taxable wages",
    "value": "1.8"
  },
  ...
]
  1. multiSelectQuestionOptions represents a form of questionOptions that will be provided if the question has a display type of MULTI_SELECT_CHECKBOX.
"multiSelectQuestionOptions": [
  {
    "id": "over65",
    "label": "I am age 65 or over"
  },
  {
    "id": "blind",
    "label": "I am blind"
  },
  {
    "id": "spouseOver65",
    "label": "My spouse is age 65 or over"
  },
  {
    "id": "spouseBlind",
    "label": "My spouse is blind"
  }
]
  1. notes represents an object of one of the following html types PARAGRAPH, IMAGE, UNORDERED_LIST, or TABLE we recommend you display related to the question. Notes are returned in order by array index and will also contain a suggested placement in relation to the questions in the question set. Suggest placement will be one of four values: ABOVE_QUESTION, BELOW_QUESTION, ABOVE_ALL_QUESTIONS, BELOW_ALL_QUESTIONS.
  • Paragraph returns a string argument and a suggest placement.
{
  "htmlType": "PARAGRAPH",
  "suggestedPlacement": "BELOW_QUESTION",
  "text": "Selecting Yes will result in selecting a marital status of Single or Married filing separately regardless of actual marital status.  See Notice 1392 for more details."
}
  • Unordered lists return an array of strings, each representing an item in the list and a suggested placement. Unordered lists may also return an optional label for the list.
{
  "htmlType": "UNORDERED_LIST",
  "suggestedPlacement": "ABOVE_ALL_QUESTIONS",
  "optionalLabel": "Form A4-MS is to be used only for employees claiming exemption from Alabama's income tax withholding requirements based on the conditions set forth under the Military Spouses Residency Relief Act (P.L. 111-97). In order to qualify for this exemption, the employee must be able to answer true to all of the following conditions:",
  "listItems": [
    "My Spouse is an active duty military servicemember",
    "I am not a military servicemember",
    "My Spouse's current military orders assign him/her to a location in/near Alabama",
    "I am present in/near Alabama solely to be with my servicemember Spouse",
    "I and my military service member Spouse live at the same address",
    "My domicile is a state other than Alabama",
    "My military servicemember Spouse's domicile is the same as mine, or I will be selecting my Spouse's domicile for tax purposes"
  ]
}
  • Image returns the href of the image itself, a suggested placement, and may an optional label. SPF API provides images of each page of every unfilled withholding form, with a standard width of 1280 pixels, that can be displayed to allow users to view the form without needing to render the pdf. Other images, returned for question context, will always have a maximum width of 800 pixels.
{
  "htmlType": "IMAGE",
  "suggestedPlacement": "ABOVE_ALL_QUESTIONS",
  "href": "https://spfcdn.symmetry.com/images/NC102/1.20.0/nc102_allowances-table.png"
}
  • Tables return an object containing an array of headers and an array of rows, representing the respective cells in each row. Table note types will also include a suggested placement and may include an optional label. An example can be found below.
{
  "htmlType": "TABLE",
  "suggestedPlacement": "ABOVE_ALL_QUESTIONS",
  "headers": [
    "Filing Status & Dependents",
    "Income range from all sources"
  ],
  "rows": [
    {
      "cells": [
        "Single",
        "$12,493 to $14,900"
      ]
    },
    {
      "cells": [
        "Married Filing Jointly (1 or less dependents)",
        "$21,068 to $24,800"
      ]
    },
    {
      "cells": [
        "Married Filing Jointly (2 or more dependents)",
        "$25,356 to $30,800"
      ]
    },
    {
      "cells": [
        "Head of Household/Qualifying Widow(er) (1 or less dependents)",
        "$17,762 to $21,600"
      ]
    },
    {
      "cells": [
        "Head of Household/Qualifying Widow(er) (2 or more dependents)",
        "$21,173 to $24,800"
      ]
    }
  ]
}

Navigation Objects:
navigationType represents one of the two types of navigation a question can take (CONSTANT or VARIABLE).

Constant navigation objects represent static navigation, in which, the next question set is always the next question set, regardless of the answers to questions contained within the current question set. Variable navigation objects represent dynamic navigation, in which, the next question is dependent upon the selected answer for question within the current question set.

A constant navigation object will consist of the following:

  1. navigationType with a value of CONSTANT
  2. navigationMapping which will contain a boolean flag hasMoreQuestions.
  • When hasMoreQuestions with a value of true represents that there are additional question sets that need to be completed. The navigationMapping will include nextQuestionSetId for the ID of the next question set.
"navigation": {
  "navigationType": "CONSTANT",
  "navigationMapping": {
    "nextQuestionSetId": "QS9",
    "hasMoreQuestions": true
  }
}
  • When hasMoreQuestions with a value of false represents that there are no more questions to be asked. The navigationMapping will not include nextQuestionSetId and will include a perjury statement from the form that should be displayed.
"navigation": {
  "navigationType": "CONSTANT",
  "navigationMapping": {
    "hasMoreQuestions": false,
    "perjuryStatement": "Under penalties of perjury, I certify that I have examined this certificate and to the best of my knowledge and belief, it is true, correct, and complete."
  }
}

A variable navigation object will consists of the following:

  1. navigationType with a value of VARIABLE
  2. questionId will contain the ID of the question for which the associated answer determines the next question.
  3. navigationMappings represent a mapping of answers to the question referenced by questionId. Based on the answer provided by the user, one mapping should be used to determine if additional questions exist, and if so, the next question set ID. Each mapping may contain the following:
  • hasMoreQuestions is a boolean that represent if there are additional questions. A value of true means that there are additional question sets that need to be completed. A value of false means that you this question is the last question in the question set.
  • nextQuestionSetId will return a string value in the format (QS#) giving the next question set id OR the string "INELIGIBLE". "INELIGIBLE" represents that based on this answer the employee is not eligible to complete the form. As with constant navigation types, nextQuestionSetId will not be returned if hasMoreQuestions is false.
  • perjuryStatement will return a string. This string represents a form specific, legal statement, that must be agreed upon before signing the form. A form can have one or more perjury statements based upon the question set process and these can contain dynamic variables that must be parsed and replaced.

The example below represents a variable navigation object, in which, the next question set is dependent on the user's selected answer to the question "filingStatus" in the current question set. If the user had select "EXEMPT", we can see that the hasMoreQuestions is false and a perjury statement is available to display to the user. Otherwise, selecting any other filing status value would result in additional questions, with the next question ID of "QS2".

"navigation": {
        "navigationType": "VARIABLE",
        "questionId": "filingStatus",
        "navigationMappings": {
            "EXEMPT": {
                "hasMoreQuestions": false,
                "perjuryStatement": "Under penalties of perjury, I certify that I have examined this certificate and to the best of my knowledge and belief, it is true, correct, and complete."
            },
            "MARRIED_FILING_SEPARATELY": {
                "nextQuestionSetId": "QS2",
                "hasMoreQuestions": true
            },
            "MARRIED": {
                "nextQuestionSetId": "QS2",
                "hasMoreQuestions": true
            },
            "HEAD_OF_FAMILY": {
                "nextQuestionSetId": "QS2",
                "hasMoreQuestions": true
            },
            "SINGLE": {
                "nextQuestionSetId": "QS2",
                "hasMoreQuestions": true
            }
        }
    }

In the next example, we show an example of dynamic perjury statements. A dynamic perjury statement is simply a perjury statement that requires a value to be added to the statement itself. This is done through parsing and replacing the variable within the perjury statement string. Variables within perjury statement will be wrapped by double curly braces ("{{ }}"), with the contents representing questionId. The perjury statement should be checked for variables, and updated as appropriate with the user's answer to the respective question before displaying.

Known forms that have dynamic perjury statements are the AZ107, IL102, IN102, NY105, WI103, and the WV102.

"navigation": {
  "navigationType": "CONSTANT",
  "navigationMapping": {
    "hasMoreQuestions": false,
    "perjuryStatement": "I declare under penalties of perjury that I am a resident of the state of {{ reciprocalResidentState }}"
  }
}

🚧

Before Moving On From Navigation!

Due to the nature of using a VARIABLE navigation type, the next page (question set) could have multiple paths. As specifically designed, our API returns "navigationMappings" as there could be more than one path.

In contrast, using a CONSTANT navigation type, the next page (question set) is always the next question with no variance. Therefore, by design our API will return "navigationMapping" as there is only one path.

Capturing and Storing Answers

Client implementations of the SPF-API are responsible for capturing and storing answers from each question set.

Once all answers have been collected, they can be submitted to the fillPdf (POST) endpoint to generate the completed form and relevant tax parameters.

Language Support

The formQuestionSet endpoint also provides language support for English, Spanish, and French using the Accept-Language request HTTP header.

Support for Multiple Form Revisions

SPF-API can support multiple form versions or revisions. Specifying a form version can be accomplished with a query parameter. You can collect all the details of a form and its version from the fillPdf​/{formId} (GET) endpoint. This is an optional query param and if not included, will provide the latest form version.

🚧

Form Versioning Syntax Example

formQuestionSet/{{formId}}/{{questionSetId}}?formVersion={{formVersion}}

❗️

Form Versions

The SPF-API will typically support the current version or revision of a form. As a convenience, SPF-API will sometimes support the previous version or revision of a form. This is provided as a short-term convenience to allow endpoint clients to transition to new revisions of a form when substantial engineering effort may be required to implement a new form revision.

It is not recommended to target a specific revision of a form as a long-term workaround. Typically, previous versions of a form are removed from the SPF-API catalog three to six months after it has been succeeded with a new revision.


What’s Next
Jump to top