Full Apply

This page specifies how to formulate a request to apply to a job which requires a more thorough application request. The JSON response from the /jobs/search endpoint for such jobs will meet the follow two criteria:

  1. The apply_method field for the job will equal "full_apply_endpoint"
  2. The number_of_questions_allowed field will have a value than 0

The email, full name, questions and the Partner Attributes contained in the Job Search response are required. As with almost all other requests to the Apploi API, the X-Api-Key header is required in the request and should contain the API key given to the partner to be able to use the API.

Questions For Apploi Jobs.

A JSON representation of the questions related to a job can be retrieved using the questions_url field that is contained in the response from the /jobs/search endpoint. The questions_url should look like this:

'https://ats-integrations.apploi.com/v1/apploi/{{job_id}}/questions.json?source=None'

In fact, that template should be usable in place of a call to the /jobs/search endpoint, by replacing {{job_id}} with the ID of the specific job for which question data needs to be retrieved. If using that template doesn't work, then an explicit call to the /jobs/search endpoint can/should be made.

Example Questions Data

Here is an example of the some of the JSON body returned from a call to the questions_url URL mentioned above. The example contains an entry for a few of the most common types of available questions: textarea, date, select, and multiselect.

{
  "questions_en": [
    {
      'id': 1893206,
      'question': 'Do you currently hold an active license as a professional counselor for the state of Texas?',
      'required': True,
      'type': 'textarea'
    },
    {
       'id': 1920662,
       'question': 'Date of Birth',
       'required': True,
       'type': 'date',
       'format': 'dd/MM/yyyy'
    },
    {
       'id': 1854409,
       'question': 'Do you require Visa sponsorship? ',
       'required': True,
       'type': 'select',
       'options': [{'value': 0, 'label': 'Yes'}, {'value': 1, 'label': 'No'}]
    },
    {
      'id': 1841349,
      'question': 'Do you have MHE Experience? (Forklift, Stockpicker, Reach Truck)',
      'required': True,
      'type': 'multiselect',
      'options': [
        {'value': 0, 'label': 'No experience; Need Training'},
        {'value': 1, 'label': 'Experienced/Certified'},
        {'value': 2, 'label': 'Some Experience; Require Additional Training'}
      ]
		}
	],
	"questions_es": ["This will contain an array with the same data, but in Spanish"]
}

Question Schema

The following table provides an explanation of the specific fields returned in response to a call to the questions_url .

NameSchemaDescriptionAllowed valuesExample
idYesUnique ID for this question.any string"Yes"
typeYesDescribes the type of question. Refer to the following sections for a full explanation of each question type.
  • text
  • select
  • multiselect
  • date
questionYesThe question text that is displayed to applicants.any string“Veteran status?”
formatYes for type=date or type=textUse the format for date or integer answers
  • "dd/MM/yyyy"
  • integer
maxYes  format=integerFor format=integerany integer for format=integer100
minYes  format=integerFor format=integerany integer for format=integer0
requiredYesAllows the setting of a question as required. When required:true is present, Indeed Apply forces a non-empty text field or a non-empty selection. When the required parameter is not present, questions are optional. If a question is optional, a possible answer value is “”.
  • true
  • false
OptionsYes for type=select and type=multiselectThe list of options available in the drop-down menu.JSONThis page specifies how to formulate a re
LabelYes for type=select and type=multiselectThe text for options in any drop-down menu.any stringFemale
ValueYes for type=select and type=multiselectThe value corresponding to each option that will be returned for questions with drop-down menus.any string1

Property by Question Type

Not all of the possible fields will be provided for every question returned in the response. Some of the fields returned will be included based on the type of question.

Property/Question TypeTextSelectMultipleDateSlider(number)
id✅✅✅✅✅
question✅✅✅✅✅
required✅✅✅✅✅
format❌❌❌✅✅
label❌✅✅❌❌
value❌✅✅❌❌
min❌❌❌❌✅
max❌❌❌❌✅

Answer Schema

The expected schema for answer objects in a request is provided below. Each answer is checked against the job's questions. An ID that isn't one of the job's questions, a select or multiselect option that doesn't exist, a missing value or values, or a date in another format returns 400, with a developer_message that explains the problem. Answers to text and slider questions aren't checked this way. An empty answer, or a slider answer that isn't a number, is not saved, and no error is returned. Required questions are not enforced, so make sure the applicant answers them.

ParameterValueRequiredDescription
idNumeric stringYesThe ID of the question for which this object provides an answer. The available question ids can be obtained from the question data retrieved as described above.
valueStringYes (for all answer types except multiselect)The string provided by this key/value pair represents the answer to the question. The precise string depends on what type of answer is required. The type of answer and the possible values are defined in the example questions data above. Different examples are also provided in the sample request above.
valuesArray of stringsYes (for multiselect answers)
No (for all other answer types)
An array of numeric strings. Each item in the array represents an answer option for a question which requires a multiselect answer.

Answer types and associated formats

Here is another way of representing the data above, classified by answer type.

Answer TypeJSON Format
text, slider{"id":string, "value":string}
select{"id":string, "value": "numeric_string"}
calendar{"id":string, "value": string(dd/MM/yyyy)}
multiselect{"id":string, "values": [string_1, string_2, string_n]}

Example Request

URL

Note: Check the /easy-apply documentation and remember to add in the headers the x-api-key.

https://partners.apploi.com/easy-apply

Body

{
    "applicant": {
        "email": "{{email}}",
        "fullName": "{{fullName}}",
        "phoneNumber": "{{phoneNumber}}",
        "resume": {}
    },
    "get_person_token": 1,
    "enqueue": true,
    "job": {
        "jobId": "{{job_id}}"
    },
    "partner_attributes": {
        "sponsored": true,
        "redirect_apply": "https://jobs-univision.apploi.com/view/468036?utm_campaign=integration&utm_medium=job-board-search&utm_source=<utm_source>-boosted&ajs_event=LOAD_JOB_PAGE&ajs_aid=3f617530-4530-11e9-a2ca-7e775511d9e3&ajs_prop_search_fetch_id=efb330ceef1b44e3b07328adae1b1ccd&ajs_prop_keyword=&ajs_prop_page=1&ajs_prop_city_center=Unselected&ajs_prop_search_order=5&ajs_prop_job_id=468036&ajs_prop_doc_type=preferredjob&ajs_prop_job_location_lat=39.3798123&ajs_prop_job_location_lon=-74.5302804&ajs_prop_boosted=1&ajs_prop_utm_source=<utm_source>-boosted&ajs_prop_utm_medium=job-board-search&ajs_prop_utm_campaign=integration",
        "utm_medium": "job-board-search",
        "utm_campaign": "integration",
        "keyword": "",
        "search_fetch_id": "efb330ceef1b44e3b07328adae1b1ccd",
        "page": 1,
        "order": 5,
        "city_center": "Unselected",
        "utm_source": "<exploratory>-full-apply-boosted",
        "source": "<source>-apply"
    },
"questions": {
    "answers": [
      // "text" type answer
      {
        "id": "1786806",
        "value": "Text 1"
      },
      // "text" type answer
      {
        "id": "1786807",
        "value": "Text Optional"
      },
      // "text" type answer for a question that requires a numerical answer
      {
        "id": "1786810",
        "value": "20"
      },
      // "text" type answer for a question that requires a numerical answer
      {
        "id": "1786811",
        "value": "90"
      },
      // "calendar" type answer
      {
        "id": "1786812",
        "value": "17/11/2020"
      },
      // "calendar" type answer
      {
        "id": "1786813",
        "value": "18/11/2020"
      },
      // "select" type answer
      {
        "id": "1786824",
        "value": "0"
      },
      // "select" type answer
      {
        "id": "1786825",
        "value": "1"
      },
      // "multiselect" type answer
      {
        "id": "1786826",
        "values": [
          "0"
        ]
      },
      // "multiselect" type answer
      {
        "id": "1786827",
        "values": [
          "1",
          "2"
        ]
      }
    ]
  }
}

Request explained

ParameterValueRequiredDescription
get_person_token1NoFlag used to request bearer_token, allowing you to make requests related to the user. Check Other useful endpoints
enqueuetrueNo
resumeJSON ObjectNoTo check the entire structure go to /easy-apply
questionsJSON ObjectNo

An object containing data about answers to the questions defined for applications to a given job. The data for the questions is described in the Questions For Apploi Jobs section above.

The questions object, if present, contains one single key, answers, the value of which is an array of JSON objects.
The full request schema for the JSON object for each item in the answers array is described in the Answer Schema section above.

Examples

Applying to a Full Apply job takes three calls:

  1. Look the job up with /jobs/search. If its external_url is set, the employer takes applications on its own site, so send the applicant to its redirect_apply_url instead. Otherwise, check that its apply_method is full_apply_endpoint, and keep its questions_url and partner_attributes.
  2. Get the job's questions from its questions_url. Fetching it does not need your API key.
  3. Send the application to /easy-apply, with one answer per question in questions.answers.

Each answer's form depends on the question's type, as described in Answer types and associated formats. Send every id and every value as a string. For select and multiselect questions, send the chosen option's value.

Replace your_partner_name with the partner name agreed with Apploi.

The examples read your API key from an environment variable named APPLOI_API_KEY. Before running them, set APPLOI_API_KEY in your own environment to the Partner API key you received from Apploi. The examples do not set it for you, and the key should never be written into your code.

The Python, TypeScript and Ruby examples answer each question with a placeholder: the first option, a fixed date, or sample text. Replace these with the applicant's real answers. For everything a response can mean, see What the response means on the /easy-apply reference page.

# APPLOI_API_KEY must hold the Partner API key you received from Apploi.
# These examples do not set it for you: set it yourself, in your own
# environment, before running them. For example, in your shell:
#   export APPLOI_API_KEY="<the API key you received from Apploi>"

# 1. Look up the job. Send the applicant to "redirect_apply_url" instead if
#    "external_url" is set. Otherwise check that "apply_method" is
#    "full_apply_endpoint", and note its "questions_url" and
#    "partner_attributes".
curl -G https://partners.apploi.com/jobs/search \
  -H "X-Api-Key: $APPLOI_API_KEY" \
  --data-urlencode "job_id=468003" \
  --data-urlencode "source=your_partner_name"

# 2. Get the job's questions: use the "questions_url" from step 1. Fetching it
#    does not need your API key.
curl -sS "https://ats-integrations.apploi.com/v1/apploi/468003/questions.json?source=your_partner_name"

# 3. Send the application, with one answer per question from step 2, and the
#    job's partner_attributes from step 1, unchanged. The answers below are for
#    the example questions shown earlier on this page.
curl -sS -X POST https://partners.apploi.com/easy-apply \
  -H "X-Api-Key: $APPLOI_API_KEY" \
  -H "Content-Type: application/json" \
  -w "\nHTTP status: %{http_code}\n" \
  -d '{
    "applicant": {
      "email": "[email protected]",
      "fullName": "Jane Doe",
      "phoneNumber": "555-010-0123"
    },
    "job": {
      "jobId": "468003"
    },
    "partner_attributes": {
      "sponsored": true,
      "search_fetch_id": "394f44dc0aff4ee6ae469221e8934ee8",
      "page": 1,
      "order": 1
    },
    "questions": {
      "answers": [
        { "id": "1893206", "value": "Yes, license LPC-80421" },
        { "id": "1920662", "value": "21/04/1990" },
        { "id": "1854409", "value": "1" },
        { "id": "1841349", "values": ["0", "2"] }
      ]
    }
  }'
import os

import requests

# APPLOI_API_KEY must hold the Partner API key you received from Apploi.
# These examples do not set it for you: set it yourself, in your own
# environment, before running them. For example, in your shell:
#   export APPLOI_API_KEY="<the API key you received from Apploi>"
API_KEY = os.environ["APPLOI_API_KEY"]

def submit_application(body):
    response = requests.post(
        "https://partners.apploi.com/easy-apply",
        headers={"X-Api-Key": API_KEY},
        json=body,
        timeout=60,
    )
    if response.status_code == 200:
        return "submitted", response.json()
    if response.status_code == 409:
        # The applicant has already applied to this job. Don't retry.
        return "already_applied", response.json()
    if response.status_code == 412:
        # Only sent when "enqueue" is False. Don't retry.
        result = response.json()
        if "job_id" in (result.get("errors") or {}):
            # The job is closed or doesn't exist.
            return "job_closed", result
        # The application already exists, for example from an earlier attempt.
        return "already_applied", result
    # 400: fix the request before sending it again. 5xx: retry with backoff.
    raise RuntimeError(f"Request failed with status {response.status_code}: {response.text}")

def answer_for(question):
    # Placeholder answers: replace them with the applicant's real answers.
    question_id = str(question["id"])  # ids are sent as strings
    if question["type"] == "select":
        return {"id": question_id, "value": str(question["options"][0]["value"])}
    if question["type"] == "multiselect":
        return {"id": question_id, "values": [str(option["value"]) for option in question["options"][:1]]}
    if question["type"] == "date":
        return {"id": question_id, "value": "21/04/1990"}  # DD/MM/YYYY
    if question["type"] == "text":
        # "format": "integer": a whole number from "min" to "max", as a string
        return {"id": question_id, "value": str(question["min"])}
    return {"id": question_id, "value": "Sample answer"}  # textarea

# 1. Look up the job, and check that it takes Full Apply.
response = requests.get(
    "https://partners.apploi.com/jobs/search",
    headers={"X-Api-Key": API_KEY},
    params={"job_id": "468003", "source": "your_partner_name"},
    timeout=30,
)
response.raise_for_status()
jobs = response.json()["data"]
if not jobs:
    raise RuntimeError("Job 468003 was not found")
job = jobs[0]
if job.get("external_url"):
    # The employer takes applications on its own site.
    raise RuntimeError(f"Send the applicant to {job['redirect_apply_url']}")
if job["apply_method"] != "full_apply_endpoint":
    raise RuntimeError(f"Job 468003 uses {job['apply_method']}, not Full Apply")

# 2. Get the job's questions. Fetching questions_url does not need your API key.
response = requests.get(job["questions_url"], timeout=30)
response.raise_for_status()
questions = response.json()["questions_en"]

# 3. Send the application with the answers.
body = {
    "applicant": {
        "email": "[email protected]",
        "fullName": "Jane Doe",
        "phoneNumber": "555-010-0123",
    },
    "job": {"jobId": job["id"]},  # a string, not a number
    "partner_attributes": job["partner_attributes"],  # sent back unchanged
}
answers = [answer_for(question) for question in questions]
if answers:
    body["questions"] = {"answers": answers}

outcome, result = submit_application(body)
print(outcome, result)
// APPLOI_API_KEY must hold the Partner API key you received from Apploi.
// These examples do not set it for you: set it yourself, in your own
// environment, before running them. For example, in your shell:
//   export APPLOI_API_KEY="<the API key you received from Apploi>"
if (!process.env.APPLOI_API_KEY) {
  throw new Error("Set APPLOI_API_KEY to the Partner API key you received from Apploi");
}
const API_KEY: string = process.env.APPLOI_API_KEY;

type Outcome = "submitted" | "already_applied" | "job_closed";

async function submitApplication(body: object): Promise<{ outcome: Outcome; result: any }> {
  const response = await fetch("https://partners.apploi.com/easy-apply", {
    method: "POST",
    headers: { "X-Api-Key": API_KEY, "Content-Type": "application/json" },
    body: JSON.stringify(body),
  });
  if (response.status === 200) {
    return { outcome: "submitted", result: await response.json() };
  }
  if (response.status === 409) {
    // The applicant has already applied to this job. Don't retry.
    return { outcome: "already_applied", result: await response.json() };
  }
  if (response.status === 412) {
    // Only sent when "enqueue" is false. Don't retry.
    const result = await response.json();
    if (result.errors?.job_id !== undefined) {
      // The job is closed or doesn't exist.
      return { outcome: "job_closed", result };
    }
    // The application already exists, for example from an earlier attempt.
    return { outcome: "already_applied", result };
  }
  // 400: fix the request before sending it again. 5xx: retry with backoff.
  throw new Error(`Request failed with status ${response.status}: ${await response.text()}`);
}

type Answer = { id: string; value: string } | { id: string; values: string[] };

function answerFor(question: any): Answer {
  // Placeholder answers: replace them with the applicant's real answers.
  const id = String(question.id); // ids are sent as strings
  switch (question.type) {
    case "select":
      return { id, value: String(question.options[0].value) };
    case "multiselect":
      return { id, values: question.options.slice(0, 1).map((option: any) => String(option.value)) };
    case "date":
      return { id, value: "21/04/1990" }; // DD/MM/YYYY
    case "text":
      // "format": "integer": a whole number from "min" to "max", as a string
      return { id, value: String(question.min) };
    default:
      return { id, value: "Sample answer" }; // textarea
  }
}

// 1. Look up the job, and check that it takes Full Apply.
const lookupParams = new URLSearchParams({ job_id: "468003", source: "your_partner_name" });
const lookupResponse = await fetch(`https://partners.apploi.com/jobs/search?${lookupParams}`, {
  headers: { "X-Api-Key": API_KEY },
});
if (!lookupResponse.ok) {
  throw new Error(`Request failed with status ${lookupResponse.status}`);
}
const job = (await lookupResponse.json()).data[0];
if (!job) {
  throw new Error("Job 468003 was not found");
}
if (job.external_url) {
  // The employer takes applications on its own site.
  throw new Error(`Send the applicant to ${job.redirect_apply_url}`);
}
if (job.apply_method !== "full_apply_endpoint") {
  throw new Error(`Job 468003 uses ${job.apply_method}, not Full Apply`);
}

// 2. Get the job's questions. Fetching questions_url does not need your API key.
const questionsResponse = await fetch(job.questions_url);
if (!questionsResponse.ok) {
  throw new Error(`Request failed with status ${questionsResponse.status}`);
}
const questions: any[] = (await questionsResponse.json()).questions_en;

// 3. Send the application with the answers.
const answers = questions.map(answerFor);
const { outcome, result } = await submitApplication({
  applicant: {
    email: "[email protected]",
    fullName: "Jane Doe",
    phoneNumber: "555-010-0123",
  },
  job: { jobId: job.id }, // a string, not a number
  partner_attributes: job.partner_attributes, // sent back unchanged
  ...(answers.length > 0 && { questions: { answers } }),
});
console.log(outcome, result);
require "json"
require "net/http"
require "uri"

# APPLOI_API_KEY must hold the Partner API key you received from Apploi.
# These examples do not set it for you: set it yourself, in your own
# environment, before running them. For example, in your shell:
#   export APPLOI_API_KEY="<the API key you received from Apploi>"
API_KEY = ENV.fetch("APPLOI_API_KEY")

def submit_application(body)
  uri = URI("https://partners.apploi.com/easy-apply")
  request = Net::HTTP::Post.new(uri)
  request["X-Api-Key"] = API_KEY
  request["Content-Type"] = "application/json"
  request.body = JSON.generate(body)

  response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true, read_timeout: 60) do |http|
    http.request(request)
  end

  case response.code
  when "200" then [:submitted, JSON.parse(response.body)]
  # The applicant has already applied to this job. Don't retry.
  when "409" then [:already_applied, JSON.parse(response.body)]
  # Only sent when "enqueue" is false. Don't retry.
  when "412"
    result = JSON.parse(response.body)
    # With errors.job_id, the job is closed or doesn't exist. Otherwise the
    # application already exists, for example from an earlier attempt.
    (result["errors"] || {}).key?("job_id") ? [:job_closed, result] : [:already_applied, result]
  # 400: fix the request before sending it again. 5xx: retry with backoff.
  else raise "Request failed with status #{response.code}: #{response.body}"
  end
end

def answer_for(question)
  # Placeholder answers: replace them with the applicant's real answers.
  id = question["id"].to_s # ids are sent as strings
  case question["type"]
  when "select" then { id: id, value: question["options"][0]["value"].to_s }
  when "multiselect" then { id: id, values: question["options"].first(1).map { |option| option["value"].to_s } }
  when "date" then { id: id, value: "21/04/1990" } # DD/MM/YYYY
  # "format": "integer": a whole number from "min" to "max", as a string
  when "text" then { id: id, value: question["min"].to_s }
  else { id: id, value: "Sample answer" } # textarea
  end
end

# 1. Look up the job, and check that it takes Full Apply.
uri = URI("https://partners.apploi.com/jobs/search")
uri.query = URI.encode_www_form(job_id: "468003", source: "your_partner_name")
request = Net::HTTP::Get.new(uri)
request["X-Api-Key"] = API_KEY

response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true, read_timeout: 30) do |http|
  http.request(request)
end
raise "Request failed with status #{response.code}" unless response.is_a?(Net::HTTPSuccess)

job = JSON.parse(response.body)["data"].first
raise "Job 468003 was not found" if job.nil?
# The employer takes applications on its own site.
raise "Send the applicant to #{job["redirect_apply_url"]}" if job["external_url"]
raise "Job 468003 uses #{job["apply_method"]}, not Full Apply" unless job["apply_method"] == "full_apply_endpoint"

# 2. Get the job's questions. Fetching questions_url does not need your API key.
response = Net::HTTP.get_response(URI(job["questions_url"]))
raise "Request failed with status #{response.code}" unless response.is_a?(Net::HTTPSuccess)
questions = JSON.parse(response.body)["questions_en"]

# 3. Send the application with the answers.
body = {
  applicant: {
    email: "[email protected]",
    fullName: "Jane Doe",
    phoneNumber: "555-010-0123"
  },
  job: { jobId: job["id"] }, # a string, not a number
  partner_attributes: job["partner_attributes"] # sent back unchanged
}
answers = questions.map { |question| answer_for(question) }
body[:questions] = { answers: answers } unless answers.empty?

outcome, result = submit_application(body)
puts outcome, result

Example Response

{
    "developer_message": "Submit Successful",
    "errors": null,
    "info": {
        "person_id": 32424609,
        "bearer_token": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6MzI0MjQ2MDksInJvbGVzIjpbImFwcGxpY2FudCJdLCJleHAiOjE2MTMxNTgxMjF9.vQuM-HDaPliAW53RO0f99Ti9px6dOnIY_nBJAU4F-K0",
        "job_id": "598551"
    }
}

Status Code Response

  • 200 (Successful): The application was accepted. With the default "enqueue": true, a job that has closed also returns 200, and the application is then dropped.
  • 409 (Conflict): The applicant has already applied to this job, matched on email address. This happens unless the team has repeat applications turned on.
  • 412 (Precondition Failed): Only sent when "enqueue" is false. If errors has a job_id, the job is closed or doesn't exist; otherwise the application already exists.
  • 400 (Bad Request): The request or one of its answers is invalid. developer_message says what's wrong.

See Errors for the other status codes.