Creating applications
Create a candidate for the application
When using Kombo’s “create application” endpoint you don’t have to worry about creating a candidate for the application, we take care for you of these possible cases:
- there is no candidate in the system → we will create it for you
- there already is a candidate in the system → we will just attach the application to the new candidate
Set the source of the application
When creating an application you want to let your customers know that this application is coming from you. Otherwise they won’t have a clue how many of their hires were because of your candidates and therefore your created value.
Our “create application” endpoint will automatically set the source of the candidate for you on every created application (see more here)
🦉 Most ATS set the source on the candidate level (not the application level). If you are creating an application for a candidate that has existed in the system before, we won’t override the source.
We will by default use the name of your environment (if your env is called “Example Customer (Prod)” the source will show up as “Example Customer”. If you want to change this name, please reach out to us.
Some ATS will require the user to configure the source in their ATS settings. In those cases, we will ask your customer to do so as part of connecting their ATS and provide additional help in our help-center.
How to capture the source of candidates in an ATS – Kombo
Upload attachments with the application
You can upload any number of attachments to an application via the attachments
array in the request body. The first CV in the attachments will be treated as the resume of the candidate when the tool allows previewing a resume.
There are two ways to submit the document that will be uploaded:
- as a base64 string: you can encode the attachment and put it into the
attachments.[*].data
field. This will cause your requests and logs to be quite large, though. - via a download URL (recommended): if you don’t want to work with large base64 strings, you can provide a download URL to the
attachments.[*].data_url
field.
Those two ways are mutually exclusive. You can’t submit both data
and data_url
at the same time.
♻️ What happens if there is an invalid attachment submitted?
If the attachment is rejected by the ATS, we will retry creating the application without the attachment. If the request then succeeds, the application will be created without the attachment and you will receive a successful response that has one record per failed attachment in the warnings
array
{
"status": "success",
"data": ...,
"warnings": [
{
"message": "string"
}
]
}
Retry behaviour
We have implemented retry logic on our side. That means we’ll retry errors with status code 429
, any 5xx
error and if we get no response at all
Therefore, please only retry requests if you get a 5xx
error from Kombo because that means our system wasn’t able to resolve the issue on its own because there was downtime or an unexpected error.
Write answers to screening questions
If you want to submit the answer to a screening question to the ATS you have to first read the questions that are asked per job. You will get this info by having the “Read screening questions” scope enabled in your scope config.
You will then get the questions along with possible answers delivered on each job:
{
"status": "success",
"data": {
"next": "eyJwYWdlIjoxMiwibm90ZSI6InRoaXMgaXMganVzdCBhbiBleGFtcGxlIGFuZCBub3QgcmVwcmVzZW50YXRpdmUgZm9yIGEgcmVhbCBjdXJzb3IhIn0=",
"results": [
{
"id": "26vafvWSRmbhNcxJYqjCzuJg",
"remote_id": "32",
"name": "Backend Engineer",
...
"screening_questions": [
{
"id": "26vafvWSRmbhNcxJYqjCzuJg",
"remote_id": "48b4d36a-1d4b-4c50-ada7-9519078e65b4",
"title": "Which is your primary programming language?",
"description": "Please enter the language you are most comfortable with.",
"format": {
"display_type": "SINGLE_LINE",
"max_length": null,
"type": "TEXT"
},
"index": 0,
"required": true
}
]
}
]
}
}
There are different type
s of screening questions:
TEXT
NUMBER
FILE
SINGLE_SELECT
→ the possible options will be passed informat.options
MULTI_SELECT
→ the possible options will be passed informat.options
BOOLEAN
DATE
INFORMATION
UNKNOWN
When creating the application you can pass them by providing a record for each question in the screening_question_answers
array, like so:
{
...
"screening_question_answers": [
{
// For a text question
"question_id": "26vafvWSRmbhNcxJYqjCzuJg",
"answer": "TypeScript"
},
{
// For a single-select question
"question_id" :"WA6SZ7R7YSo2C3WDLE5zmAJ",
"answer": "3WA6SZ7R7YSo2C3WDLE5zmAJ", // ID of the answer-option
},
{
// For a multi-select question
"question_id" :"WA6SZ7R7YSo2C3WDLE5zmAJ",
"answer": [
"3WA6SZ7R7YSo2C3WDLE5zmAJ", // IDs of the answer-options
"21KvMGS9Yhsbbsxfwqyb5dkF"
]
}
]
}
Be aware that some ATS have required screening questions (screening_questions[*].required == true
) and force you to submit an answer if you want to create an application. We let you know about some of the workarounds in our guide on customer-specific edgecases.
Handle ATS-specific limitations
Some ATS don’t support certain features such as screening questions. You get this information via the coverage grid or the “get tools” endpoint that will show you the supported data models, write actions and features via the coverage
object:
{
"status": "success",
"data": {
"tools": [
{
"id": "workday",
"label": "Workday",
"assets": {
"logo_url": "https://storage.googleapis.com/kombo-assets/integrations/workday/logo.svg",
"icon_url": "https://storage.googleapis.com/kombo-assets/integrations/workday/icon.svg",
"icon_black_url": "https://storage.googleapis.com/kombo-assets/integrations/workday/icon-black.svg"
},
"coverage": {
"read_models": [
{
"id": "ats_jobs",
"label": "Jobs"
},
{
"id": "ats_applications",
"label": "Applications"
},
{
"id": "ats_candidates",
"label": "Candidates"
}
],
"write_actions": [
{
"id": "ats_create_candidate",
"label": "Create candidate"
}
],
"features": [
{
"id": "automatic_source_writing",
"label": "Automatic Source Writing"
},
{
"id": "ats_sync_only_created_applications",
"label": "Sync Only Created Applications"
},
{
"id": "ats_create_candidate_full_attachments_retry",
"label": "Create Candidate Full Attachment Retry"
}
]
}
}
]
}
}
You can build logic both in your frontend and your backend based on this endpoint.
Required fields
Before you go live with your first customer, there are a few things to look out for to make the connection go as smoothly as possible.
Handling required screening questions
As mentioned in the “screening questions” section when creating applications, some ATS force you to submit the answer to a given question and will reject any applications that don’t have answers attached.
This won’t be a problem with a lot of questions but some will have the required: true
boolean set, so watch out for those.
{
"status": "success",
"data": {
"next": "eyJwYWdlIjoxMiwibm90ZSI6InRoaXMgaXMganVzdCBhbiBleGFtcGxlIGFuZCBub3QgcmVwcmVzZW50YXRpdmUgZm9yIGEgcmVhbCBjdXJzb3IhIn0=",
"results": [
{
"id": "26vafvWSRmbhNcxJYqjCzuJg",
"remote_id": "32",
"name": "Backend Engineer",
...
"screening_questions": [
{
"id": "26vafvWSRmbhNcxJYqjCzuJg",
"remote_id": "48b4d36a-1d4b-4c50-ada7-9519078e65b4",
"title": "Which is your primary programming language?",
...
"required": true
}
]
}
]
}
}
Here are our suggestions to handle this:
-
Collect the answers: you can implement a frontend component that dynamically renders a form based on the Kombo API response. The candidate can then submit the actual answers to screening questions and you can submit them to the ATS.
While this would sure be the cleanest solution, some of our customers don’t want to implement this because it violates their idea of a one-click apply solution or takes too much engineering capacity.
-
Ask customer to remove questions: if you don’t want to collect the answers to screening questions, you could ask your customer to remove these questions.
-
Clearly submit non-data: for most fields (except for dropdowns, numbers, booleans) you can “auto-fill” some data that will make it clear to your customer that this is not an actual response from the candidate. I.e. submitting an empty string or “-” for text fields, submitting a date that is very clearly in the past, etc.
✋ Be wary with this approach: your customer explicitly set those questions to be required and might not appreciate you working around their requirement.
-
Implement customer-specific logic: a less scalable way is implementing custom logic for each customer that either hard-codes data points or dynamically maps them based on other data of the candidate. We would not recommend this approach, as it’s not very scalable / maintainable with a large number of customers in production.
- Agree on preset answers: when onboarding a customer you can share the problem of required screening questions with them and agree together on a number of fixed answers for all/the most common screening questions.
Handling required custom fields
This is only a problem with SAP SuccessFactors as customers can specify custom data points per application and make them required.
The options to deal with this are the same as dealing with required screening questions, but currently the way to write back custom fields is by using the remote_fields feature.
Extend write endpoints - Kombo
SuccessFactors custom fields
-
A definition of the required properties mentioned above can be found on the
remote_data
field of your Successfactors integration’s Jobs:job.remote_data["/metadata/Candidate"]
andjob.remote_data["/metadata/JobApplication"]
This will look like the following:
{ "status": "success", "data": { "next": null, "results": [ { "id": "6aNxnud3JCdp97GDp1JjQ7ir", "remote_id": "123", "name": "Software Engineer", "job_code": null, "description": "...", "remote_data": { "/metadata/Candidate": [ { "key": "contactEmail", "type": "STRING", "label": "E-mail", "required": true, "picklistOptions": null }, ... ] ...
💡 Ask someone from Kombo to enable the reading of the remote_data
field on the Jobs model in your scope configuration if it is not already enabled.
- Add these properties to the create application request’s
remote_fields.successfactors
object, according to the following format (this is roughly pseudocode, your implementation will differ):- (Note: Only fields with
required: true
are strictly necessary to be submitted)
- (Note: Only fields with
for (var property of job.remote_data['/metadata/JobAppplication']) {
// Just a string
if (property.type === 'STRING') value = 'Text Value'
// milliseconds since UTC
if (property.type === 'DATE_TIME') value = `/Date(1704364038000)/`
// Just a number
if (property.type === 'NUMBER') value = 12345
// The ID of the selected option
// Available options can be found inside of the remote data.
if (property.type === 'PICKLIST') value = { id: selectedOptionId } // e.g. { id: "123" }
// Attachments can be uploaded with the following format
if (property.type === 'ATTACHMENT')
value = {
__metadata: {
type: 'SFOData.Attachment',
},
module: 'RECRUITING',
fileName: 'Attachment.pdf',
fileContent: '... base64 encoded file content ...',
}
// Attachments can be uploaded with the following format
if (property.type === 'MULTI_ATTACHMENT')
value = [
{
__metadata: {
type: 'SFOData.Attachment',
},
module: 'RECRUITING',
fileName: 'Attachment.pdf',
fileContent: '... base64 encoded file content ...',
},
]
}
You should end up with a create application request that looks like the following:
{
"candidate": {
"first_name": "John",
"last_name": "Doe",
"email_address": "john.doe@example.com",
"phone_number": "123412341234"
},
"remote_fields": {
"successfactors": {
"Candidate": {
"customStringField": "Some value",
"custPicklistField": {
"id": "12345"
},
"customDateField": "/Date(1234123412341234)/"
...
}
"JobApplication": {
"custPicklistField": {
"id": "56789"
},
...
}
...