1376

Create application - Kombo

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.

Enable scopes.png

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 types of screening questions:

  • TEXT
  • NUMBER
  • FILE
  • SINGLE_SELECT → the possible options will be passed in format.options
  • MULTI_SELECT → the possible options will be passed in format.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"] and
    • job.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)
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"
                },
                ...
            }
            ...