Skip to main content

Managing Direct Apply Feature

With Version 2.6, HAPI Backend now supports Direct Apply feature which enables candidates to apply directly to jobs on the job board site.


note

Only a limited set of job boards are supported for Direct Apply. The partner/ATS need to be provisioned in the HAPI system to receive applicant data. Please contact your Partner Account Manager for more information and how to get this feature enabled.

The Direct Apply consists of two parts:

  • Selecting direct apply as application method and providing the candidate questionnaire via facets / posting requirements. HAPI Elements, out-of-the-box provides the necessary UI along with ability to prefill the questionnaire.
  • Have a webhook endpoint on your backend to receive the application data which you will need to set up with your Partner Account Manager

At the time user sees the posting requirements form, there will be a select facet with set of options (example) such as:

  • Application Method
    • Email
    • URL
    • Direct Apply (new)

When the user chooses the option Direct Apply then another facet of type: QUESTIONNAIRE will appear with the necessary UI for the end user to enter questions and answers.

The HAPI Backend Question Structure

danger

The structure on HAPI Backend may change for the questionnaire and is not yet finalized. You can however use this recipe to estimate roughly the effort for implementation.

An example of how the posting_requirements (facets) send by HAPI Backend would look like is as follows:

{
"posting_requirements": [
{
"name": "applyMethod",
"label": "Apply method",
"sort": "390",
"required": true,
"type": "SELECT",
"options": [
{
"key": "url",
"label": "URL (requires additional stock / inventory)"
},
{
"key": "email",
"label": "Email",
"show": [
"emailFeedbackInsc"
]
},
{
"key": "directApply",
"label": "Direct Apply",
}
],
"message": null,
"primary_taxonomy": "job_apply_method",
"autocomplete": {
"required_parameters": null,
"parameters_source": null
},
"display_rules": null
},
{
"name": "questionnaire",
"label": "Questionnaire",
"sort": "396",
"required": false,
"type": "QUESTIONNAIRE",
"options": [],
"questionnaire": {
"text": {
"question": {
"maxLength": 490,
"minLength": 10
}
},
"types": [
"text",
"choice",
"multi-choice"
],
"choice": {
"items": {
"item": [
{
"maxLength": 150,
"minLength": 2
}
],
"maxOccurs": 5,
"minOccurs": 2
},
"question": {
"maxLength": 490,
"minLength": 10
}
},
"multi-choice": {
"items": {
"item": [
{
"maxLength": 150,
"minLength": 2
}
],
"maxOccurs": 5,
"minOccurs": 2
},
"question": {
"maxLength": 490,
"minLength": 10
}
},
"questionnaire": {
"ordered": false,
"editable": false,
"maxQuestions": 12,
"maxOpenQuestions": 4, // omitted when optional
"maxClosedQuestions": 8, // omitted when optional
"supportsRequired": false,
"supportsConditions": false
}
},
"message": null,
"primary_taxonomy": "job_questionnaire_id",
"autocomplete": null,
"display_rules": {
"show": [
{
"op": "selected_option_show_contains",
"facet": "applyMethod",
"value": null
}
]
}
}
]
}

Open Questions

Open questions refer to open-ended questions where the candidate can enter arbitrary answers.

Closed Questions

Closed questions refer to close-ended questions where the candidate should choose from answers defined by the recruiter at the time of the campaign order when they are filling the posting requirements.

The UI

The Preview UI

Most ATSs have the questions already thus will want to prefill. We have also received feedback that the end user should not be able to edit the prefilled questions; thus by default, there is a preview UI shown to the end user with no ability to edit the questions/answers unless you do the actions mentioned in these sections:

When the questions are prefilled, based on the types of questions, the end user will see a preview UI like below:


Open question example (type text):


Closed question example (type choice):


Closed question example (type multi-choice):

info

To turn the editing UI on for the end user, please refer to The Edit UI section first then to Managing the questionnaire data section

The Edit UI

When the question edit UI is turned on, based on the types of questions, the end user will see an edit UI like below:



Open question example (type text):


Closed question example (type choice and multi-choice):

info

To turn the editing UI on for the end user, please refer to Managing the questionnaire data section

Managing the questionnaire data

In order to prefill the questionnaire, there are a couple of steps to take:

  • To know when the end user has added a contract with the channel that supports direct apply to HAPI Elements basket
  • To find the application method posting requirement
  • To find the application method posting requirement's direct apply option
  • Make direct apply option selected for application method posting requirement so that questionnaire appears in the UI
  • To find the questionnaire posting requirement(s)
  • To find limits of the questionnaire in terms of types, limits of questions by type
  • Filter out your own set of questions and answers based on the limits of the questionnaire of the particular channel
  • To prefill the questionnaire posting requirements(s)
  • Showing the Edit UI (not required) or Showing the Preview UI but letting user decide whether to edit (not required)

HAPI Elements provides two service functions to make it easy to prefill the questionnaire posting requirement, so you don't have to do most of the steps above.

Using the prefill function (opinionated)

This function will take in a channel name and array of questions in the expected shape as arguments then automatically do the steps above. You only need to transform your set of questions to the specified shape then pass it as an argument.

danger

The structure on HAPI Backend may change for the questionnaire and is not yet finalized. You can however use this recipe to estimate roughly the effort for implementation.

caution

This function ignores the questions behind the scenes (thus being opinionated). Refer to the unopinionated but a bit more complex way of providing questions for more information on how to handle this the most user-friendly way.


The channel name argument can be found in our contract utilities submodule and can be accessed, for example as window.hapi.contract.utils.directApplyChannelName.SEEK_AUSTRALIA


The array of expected questions shape can be found on contract types as ContractDirectApplyPostingRequirementQuestionnaireQuestionBody. Here are examples:


const openQuestion: ContractDirectApplyPostingRequirementQuestionnaireOpenQuestionBody = {
"id": "pjEbX3iRc123",
"question": "Tell us about your programming experience",
"type": "text",
"is_required": false
}

const closedQuestion: ContractDirectApplyPostingRequirementQuestionnaireClosedQuestionBody = {
"id": "pjEbX3iRc456",
"question": "Do you have bachelor's degree?",
"type": "choice", //or multi-choice
"is_required": false,
"answers": [
{
id: "pjlOjX3iRc123",
answer: "Yes",
},
{
id: "pjlOjX3iRc456",
answer: "No",
}
]
}
Using the prefill function

This function throws an error thus it needs to be wrapped with try catch.

Validation Errors
  • The array of questions passed has questions that is not the expected shape
  • The array of questions passed has questions with same ID more than once
  • The array of questions passed receives unknown types of questions
  • The array of questions passed did not contain answers array (or that you passed a non-array) for choice and multi-choice type closed questions

This function is also opinionated and ignores questions and answers of choice and multi-choice questions based on the limits of the job board and outputs a console warning with reasons as to why the questions or answers of choice and multi-choice questions are ignored when:

  • Question does not meet minimum length rule (question discarded)
  • Question does not meet maximum length rule (question discarded)
  • None of the answers provided for the choice or multi-choice question met the requirements (question discarded)
  • The questions passed exceeds maximum allowed total questions by the job board, only the first X valid question allowed per job board will be used (questions discarded)
    • There might also be maximum allowed open and closed questions which are discarded separately. For example if there are 2 valid open questions and 3 valid closed questions and job board allows 1 open, 2 closed questions, the first valid open question and the first 2 valid closed question will be used
  • The question had valid answers but did not meet minimum valid choices requirement after answers are discarded due to validation (question discarded)
  • The answers passed exceeds maximum allowed choices by the job board, only the first X valid answers allowed per job board will be used (answers discarded)
  • The question had valid answers but some of the answers are ignored (answers discarded)
    • Answer does not meet minimum length rule (answer discarded)
    • Answer does not meet maximum length rule (answer discarded)

Usage:

try {
await window
.hapi
.contract
.service
.prefillContractChannelPostingRequirementQuestionnaireForDirectApply
.run(
window
.hapi
.contract
.utils
.directApplyChannelName
.SEEK_AUSTRALIA,
[
{
"id": "pjEbX3iRc123",
"question": "Tell us about your programming experience",
"type": "text",
"is_required": false
},
{
"id": "pjEbX3iRc456",
"question": "Do you have bachelor's degree?",
"type": "choice", //or multi-choice
"is_required": false,
"answers": [
{
id: "pjlOjX3iRc123",
answer: "Yes",
},
{
id: "pjlOjX3iRc456",
answer: "No",
}
]
}
]
)
} catch (error) {
// handle error or ignore the errors
// errors can be found on /docs/recipes/contract-recipes/managing-direct-apply-feature/#validation-errors
}

This function internally calls two other service functions that are also available for you to use:

  • window.hapi.contract.service.getContractChannelPostingRequirementQuestionnaireBodyForDirectApply
  • window.hapi.contract.service.getContractChannelPostingRequirementQuestionnaireRulesForDirectApply

The function getContractChannelPostingRequirementQuestionnaireRulesForDirectApply is used to first get the rules for the particular channel. This function outputs an object of type ContractServiceGetContractChannelPostingRequirementQuestionnaireRulesForDirectApplyResponse in contract service types.


The function getContractChannelPostingRequirementQuestionnaireBodyForDirectApply is used to get the body, which does the filtering out of questions and gives back reasons as to why questions are ignored, to pass to the prefill function. This function outputs an object of type ContractServiceGetContractChannelPostingRequirementQuestionnaireBodyForDirectApplyResponse which also has the returned object combined with the response from getContractChannelPostingRequirementQuestionnaireRulesForDirectApply function.

Getting rules of the job board and deciding what questions to ignore yourself (unopinionated)

You can use these functions to decide what to ignore:

  • window.hapi.contract.service.getContractChannelPostingRequirementQuestionnaireBodyForDirectApply
  • window.hapi.contract.service.getContractChannelPostingRequirementQuestionnaireRulesForDirectApply

For most use cases, you would need to use getContractChannelPostingRequirementQuestionnaireBodyForDirectApply which already does include the object in the response returned by getContractChannelPostingRequirementQuestionnaireRulesForDirectApply. Using this function you can do:

  • get the rules of the channel's questionnaire
  • get your questions filtered out with reasons as to why some of the questions and answers are ignored
  • get the validated questions that will for sure be accepted by the job board

You can for example open a popup to the end user with the object returned by this function that will:

  • let the end user know which questions won't be accepted with reasons so let them decide what to do with them
  • let the end user edit questions in terms of length, min/max choices etc. so they pass for that particular channel

Using the function:

try {
const results: ContractServiceGetContractChannelPostingRequirementQuestionnaireBodyForDirectApplyResponse = await window
.hapi
.contract
.service
.getContractChannelPostingRequirementQuestionnaireBodyForDirectApply
.run(
window
.hapi
.contract
.utils
.directApplyChannelName
.SEEK_AUSTRALIA,
[
{
"id": "pjEbX3iRc123",
"question": "Tell us about your programming experience",
"type": "text",
"is_required": false
},
{
"id": "pjEbX3iRc456",
"question": "Do you have bachelor's degree?",
"type": "choice", //or multi-choice
"is_required": false,
"answers": [
{
id: "pjlOjX3iRc123",
answer: "Yes",
},
{
id: "pjlOjX3iRc456",
answer: "No",
}
]
}
]
)

// results object will contain the keys specified
// in the type ContractServiceGetContractChannelPostingRequirementQuestionnaireBodyForDirectApplyResponse
// decide what to do with them
const {
hasIgnoredQuestions,
ignoredQuestionDetails,
validQuestions,
rules,
directApplyVariables,
questionnairePostingRequirement,
contractInBasket
}: {
hasIgnoredQuestions: boolean
ignoredQuestionDetails: any[]
validQuestions: ContractDirectApplyPostingRequirementQuestionnaireQuestionBody[]
rules: ContractDirectApplyPostingRequirementQuestionnaireRules
directApplyVariables: ContractDirectApplyChannelPrefillVariablesMap
questionnairePostingRequirement: TransformedPostingRequirement
contractInBasket: Contract
} = results


if (hasIgnoredQuestions) {
// open a popup for example to the end user that lists
// Here are the questions that will be accepted: validQuestions
// here are the questions that are not accepted: ignoredQuestionDetails
// with details as to why they are ignored
// ignoredQuestionDetails is an array of
// ContractDirectApplyPostingRequirementQuestionnaireQuestionIgnoreReason type
// after user decides what to do with the questions
// call getContractChannelPostingRequirementQuestionnaireBodyForDirectApply function
// with the questions they came up with
// to again check if there are any questions that will be ignored
// if there are no ignored questions (hasIgnoredQuestions boolean variable)
// then call prefillContractChannelPostingRequirementQuestionnaireForDirectApply function
// with the questions they came up with

} else {
// call the prefill function with validQuestions as there are no ignored questions, all are valid
await window
.hapi
.contract
.service
.prefillContractChannelPostingRequirementQuestionnaireForDirectApply
.run(
window
.hapi
.contract
.utils
.directApplyChannelName
.SEEK_AUSTRALIA,
validQuestions
)
}
} catch (error) {
// handle error or ignore the errors
// errors can be found on /docs/recipes/contract-recipes/managing-direct-apply-feature/#validation-errors
}
ignoredQuestionDetails object with the types of warnings

When the function getContractChannelPostingRequirementQuestionnaireBodyForDirectApply returns the results object with hasIgnoredQuestions and ignoredQuestionDetails; if hasIgnoredQuestions is true, the ignoredQuestionDetails array will contain the questions and the reasons as to why they are ignored. The Typescript definition of ignoredQuestionDetails can be found in ContractDirectApplyPostingRequirementQuestionnaireQuestionIgnoreReason in contract types. The list of warnings can be found in ContractDirectApplyPostingRequirementQuestionnaireValidationWarningCode in contract enums.


There will be a data property available to process the error with an error code so you can come up with i18n for the custom message. Here are the mappings for the errors.

window.hapi.contract.utils.directApplyIgnoreReasonWarning.CLOSED_QUESTION_ANSWER_MIN_LENGTH_WARNING

The message will be Answer does not meet minimum length rule of ${minChoiceLengthRule} with data object containing the value for:

  • minChoiceLengthRule
window.hapi.contract.utils.directApplyIgnoreReasonWarning.CLOSED_QUESTION_ANSWER_MAX_LENGTH_WARNING

The message will be Answer does not meet maximum length rule of ${maxChoiceLengthRule} with data object containing the value for:

  • maxChoiceLengthRule
window.hapi.contract.utils.directApplyIgnoreReasonWarning.CLOSED_QUESTIONS_MAX_WARNING

The message will be Only the first ${maxClosedQuestionsRule} closed questions (that meet requirements) are going to be used. with data object containing the value for:

  • maxClosedQuestionsRule
window.hapi.contract.utils.directApplyIgnoreReasonWarning.CLOSED_QUESTION_ANSWER_MAX_OCCURS_WARNING

The message will be This answer was valid but is ignored because only the first ${maxOccursRule} answers (that meet the requirements) are going to be used. with data object containing the value for:

  • maxOccursRule
window.hapi.contract.utils.directApplyIgnoreReasonWarning.CLOSED_QUESTION_ANSWER_MIN_OCCURS_WARNING

The message will be This question had valid answers but did not meet minimum ${minOccursRule} valid choices requirement with data object containing the value for:

  • minOccursRule
window.hapi.contract.utils.directApplyIgnoreReasonWarning.CLOSED_QUESTION_NO_VALID_ANSWERS_WARNING

The message will be None of the answers provided for this choice or multi-choice question met the requirements with data object being empty.

window.hapi.contract.utils.directApplyIgnoreReasonWarning.CLOSED_QUESTION_SOME_ANSWERS_ARE_INVALID_WARNING

The message will be This question had valid answers but some of the answers are ignored. See reasons in data array inside this object with data object containing the choicesToIgnore array with details as to why choices are ignored. The type of choicesToIgnore is ContractDirectApplyPostingRequirementQuestionnaireQuestionAnswerIgnoreReason which can be found in contract types

window.hapi.contract.utils.directApplyIgnoreReasonWarning.OPEN_QUESTIONS_MAX_WARNING

The message will be Only the first ${maxOpenQuestionsRule} open questions (that meet requirements) are going to be used. with data object containing the value for:

  • maxOpenQuestionsRule
window.hapi.contract.utils.directApplyIgnoreReasonWarning.QUESTION_MIN_LENGTH_WARNING

The message will be Question does not meet minimum length rule of ${minQuestionLengthRule} with data object containing the value for:

  • minQuestionLengthRule
window.hapi.contract.utils.directApplyIgnoreReasonWarning.QUESTION_MAX_LENGTH_WARNING

The message will be Question does not meet maximum length rule of ${maxQuestionLengthRule} with data object containing the value for:

  • maxQuestionLengthRule
window.hapi.contract.utils.directApplyIgnoreReasonWarning.QUESTIONS_MAX_TOTAL_WARNING

The message will be Only the first ${maxQuestionsRule} questions (that meet requirements) are going to be used. with data object containing the value for:

  • maxQuestionsRule

Showing the Edit UI

In order to show the Edit UI of the questionnaire, there are a couple of steps to take:

  • To know when the end user has added a contract with the channel that supports direct apply to HAPI Elements basket
  • To find the application method posting requirement
  • To find the application method posting requirement's direct apply option
  • Make direct apply option selected for application method posting requirement so that questionnaire appears in the UI
  • To find the questionnaire posting requirement(s)

HAPI Elements provides one service function to make it easy to turn on the questionnaire posting requirement, so you don't have to do most of the steps above.

note

When you turn on the Edit UI, user will be presented with the Edit UI by default instead of the Preview UI. If you instead want the user to be presented with the Preview UI but have them decide whether to edit the questions, refer to Showing the Preview UI but letting user decide whether to edit section


Usage:

window
.hapi
.contract
.service
.setContractChannelPostingRequirementQuestionnaireOptionsDirectApply
.run(
window.hapi.contract.utils.directApplyChannelName.SEEK_AUSTRALIA,
{
shouldShowQuestionEditingTool: false,
}
)

The channel name argument can be found in our contract utilities submodule and can be accessed, for example as window.hapi.contract.utils.directApplyChannelName.SEEK_AUSTRALIA


The Typescript definition of the object passed as the options (second argument) can be found in our orderJourney types as OrderJourneyChannelPostingRequirementsStepOptions


Showing the Preview UI but letting user decide whether to edit

By default, the Preview UI is shown to the end user. If you turn on the Edit UI, then user is presented with the Edit UI by default. If you want instead the reverse where the Preview UI is shown to the end user while letting them decide whether to edit or not, you should make the edit button visible without turning on the edit UI.


The functions of the UI Submodule will be utilized like below:


Showing the Edit<>Preview toggle button that is hidden by default:

window.hapi.ui.service.showElement("[id$=-switch-edit-mode]")

Hiding the Edit<>Preview toggle button that is shown by you:

window.hapi.ui.service.hideElement("[id$=-switch-edit-mode]")

Further UI Customization

In addition, you can use the UI Submodule functions by inspecting the DOM and finding the query selectors of elements you want to customize. The recently added HTML IDs are in the 2.6 changelog for Direct Apply.