/easy-apply

Recent Requests
Log in to see full request history
TimeStatusUser Agent
Retrieving recent requests…
LoadingLoading…

This endpoint submits a job application to Apploi. It serves both API apply flows:

  • Quick Apply, for jobs whose apply_method in the /jobs/search response is easy_apply_endpoint. The job has no screening questions.
  • Full Apply, for jobs whose apply_method is full_apply_endpoint. The application also carries the applicant's answers to the job's questions.

Jobs whose apply_method is redirect can't be applied to through the API.

Examples

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.

Quick Apply

A Quick Apply request needs three things:

  • the applicant's email and fullName,
  • the job's id, as a string, in job.jobId,
  • the job's partner_attributes from the /jobs/search response, sent back unchanged.

phoneNumber is optional. Leave out get_person_token, because the bearer token it returns belongs to the deprecated bearer-token flow. To attach a resume, add applicant.resume.file, as described in the body schema on this page.

The examples define a submit_application function (submitApplication in TypeScript), which the later examples reuse. It sorts each response into the outcomes described under What the response means.

# 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>"

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": "468036"
    },
    "partner_attributes": {
      "sponsored": true,
      "search_fetch_id": "394f44dc0aff4ee6ae469221e8934ee8",
      "page": 1,
      "order": 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}")

outcome, result = submit_application({
    "applicant": {
        "email": "[email protected]",
        "fullName": "Jane Doe",
        "phoneNumber": "555-010-0123",
    },
    "job": {"jobId": "468036"},  # a string, not a number
    # Copied unchanged from the job's /jobs/search result
    "partner_attributes": {
        "sponsored": True,
        "search_fetch_id": "394f44dc0aff4ee6ae469221e8934ee8",
        "page": 1,
        "order": 2,
    },
})
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()}`);
}

const { outcome, result } = await submitApplication({
  applicant: {
    email: "[email protected]",
    fullName: "Jane Doe",
    phoneNumber: "555-010-0123",
  },
  job: { jobId: "468036" }, // a string, not a number
  // Copied unchanged from the job's /jobs/search result
  partner_attributes: {
    sponsored: true,
    search_fetch_id: "394f44dc0aff4ee6ae469221e8934ee8",
    page: 1,
    order: 2,
  },
});
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

outcome, result = submit_application(
  applicant: {
    email: "[email protected]",
    fullName: "Jane Doe",
    phoneNumber: "555-010-0123"
  },
  job: { jobId: "468036" }, # a string, not a number
  # Copied unchanged from the job's /jobs/search result
  partner_attributes: {
    sponsored: true,
    search_fetch_id: "394f44dc0aff4ee6ae469221e8934ee8",
    page: 1,
    order: 2
  }
)
puts outcome, result

What the response means

Decide what happened from the status code, not from the body. A successful response also contains a developer_message.

StatusMeaningWhat to do
200The request was accepted.Nothing. A 200 doesn't always mean an application was created. See Choosing when the application is processed for how this works by default. A developer_message of Application skipped due to inactive candidate. means the applicant's Apploi account is inactive, and no application was created.
409The applicant has already applied to this job. Applications are matched on email address.Treat it as "already applied", not as a failure. Don't retry.
412Only sent when enqueue is false. If errors has a job_id, the job is closed or doesn't exist. Otherwise the application already exists, usually because an earlier attempt to send it is still being processed.With job_id, remove the job from your listings. Otherwise, treat it like a 409. Don't retry in either case.
400The request is invalid, for example a missing email or an answer that doesn't fit its question. developer_message says what's wrong.Fix the request before sending it again. An unchanged retry fails the same way.
5xxSomething failed on Apploi's side, or the request took longer than 29 seconds.Wait, then retry with exponential backoff.

A retry after a 5xx is safe: Apploi doesn't create a second application for the same applicant and job. The exception is a team that has repeat applications turned on (they're off by default). The retry may return 200 or 409, or, with enqueue: false, a 412 without a job_id.

A 200 response looks like this:

{
  "developer_message": "Submit Successful",
  "errors": null,
  "info": {
    "person_id": 32424609,
    "job_id": "468036"
  }
}

A 409 response includes the id of the existing application:

{
  "developerMessage": "This application has already been submitted.",
  "errors": {
    "application_form_id": 918273
  }
}

A 412 for a closed or missing job names the job. A 412 for an application that already exists has saved_application_id in errors instead.

{
  "developerMessage": "Specified job doesn't exist or isn't accepting applications.",
  "errors": {
    "job_id": 468036
  }
}

Choosing when the application is processed

By default, enqueue is true. Apploi checks the request, replies, and creates the application a moment later. The response is quick, but a 200 only means the request was accepted, not that the application was created. If the job has closed, or doesn't exist, you still get 200, and the application is then dropped without notice.

Send "enqueue": false when you need to know straight away that the job is closed, for example to tell the applicant. Apploi then creates the application before replying, so the request takes longer, and a closed or missing job returns 412. If part of the application, such as the resume, can't be saved, the response is still 200, and errors lists what wasn't saved.

Some problems are reported straight away whichever value you send:

  • a request that isn't valid returns 400,
  • a repeat application returns 409.

These examples reuse submit_application (Python and Ruby) and submitApplication (TypeScript) from the Quick Apply example above.

# 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>"

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 '{
    "enqueue": false,
    "applicant": {
      "email": "[email protected]",
      "fullName": "Jane Doe"
    },
    "job": {
      "jobId": "468036"
    },
    "partner_attributes": {
      "sponsored": true,
      "search_fetch_id": "394f44dc0aff4ee6ae469221e8934ee8",
      "page": 1,
      "order": 2
    }
  }'
outcome, result = submit_application({
    "enqueue": False,
    "applicant": {"email": "[email protected]", "fullName": "Jane Doe"},
    "job": {"jobId": "468036"},
    "partner_attributes": {
        "sponsored": True,
        "search_fetch_id": "394f44dc0aff4ee6ae469221e8934ee8",
        "page": 1,
        "order": 2,
    },
})
if outcome == "job_closed":
    print("This job is no longer accepting applications.")
const { outcome } = await submitApplication({
  enqueue: false,
  applicant: { email: "[email protected]", fullName: "Jane Doe" },
  job: { jobId: "468036" },
  partner_attributes: {
    sponsored: true,
    search_fetch_id: "394f44dc0aff4ee6ae469221e8934ee8",
    page: 1,
    order: 2,
  },
});
if (outcome === "job_closed") {
  console.log("This job is no longer accepting applications.");
}
outcome, _result = submit_application(
  enqueue: false,
  applicant: { email: "[email protected]", fullName: "Jane Doe" },
  job: { jobId: "468036" },
  partner_attributes: {
    sponsored: true,
    search_fetch_id: "394f44dc0aff4ee6ae469221e8934ee8",
    page: 1,
    order: 2
  }
)
puts "This job is no longer accepting applications." if outcome == :job_closed

Full Apply

A Full Apply request is a Quick Apply request with the applicant's answers added in questions.answers. Get the job's questions from the questions_url in its /jobs/search result. The Full Apply guide describes that response.

Formatting questions.answers

Each answer has the question's id and exactly one of value or values, and nothing else. Send every id and every value as a string, even when it is a number. The answer's form depends on the question's type:

Question typeAnswerExample
textareavalue: any text{"id": "1893206", "value": "Yes, license LPC-80421"}
text, with "format": "integer"value: a whole number from 0 to 100, as a string{"id": "1877310", "value": "5"}
selectvalue: the chosen option's value{"id": "1854409", "value": "1"}
multiselectvalues: a list of the chosen options' values{"id": "1841349", "values": ["0", "2"]}
datevalue: a date in DD/MM/YYYY format{"id": "1920662", "value": "21/04/1990"}

An option's value is its position in the question's options list, counting from 0. For example, with these options:

{
  "options": [{"value": 0, "label": "Yes"}, {"value": 1, "label": "No"}]
}

the answer "No" is sent as "value": "1". A multiselect question takes values (plural); using value with a list returns 400.

Each answer is checked against the job's questions. An id that isn't one of the job's questions, an option that doesn't exist, or a date in another format returns 400, with a developer_message that explains the problem.

The examples below use the example questions from the Full Apply guide.

# 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>"

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"] }
      ]
    }
  }'
outcome, result = submit_application({
    "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"},  # textarea
            {"id": "1920662", "value": "21/04/1990"},  # date, DD/MM/YYYY
            {"id": "1854409", "value": "1"},  # select: option 1, "No"
            {"id": "1841349", "values": ["0", "2"]},  # multiselect: options 0 and 2
        ]
    },
})
print(outcome, result)
const { outcome, result } = await submitApplication({
  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" }, // textarea
      { id: "1920662", value: "21/04/1990" }, // date, DD/MM/YYYY
      { id: "1854409", value: "1" }, // select: option 1, "No"
      { id: "1841349", values: ["0", "2"] }, // multiselect: options 0 and 2
    ],
  },
});
console.log(outcome, result);
outcome, result = submit_application(
  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" }, # textarea
      { id: "1920662", value: "21/04/1990" }, # date, DD/MM/YYYY
      { id: "1854409", value: "1" }, # select: option 1, "No"
      { id: "1841349", values: ["0", "2"] } # multiselect: options 0 and 2
    ]
  }
)
puts outcome, result
Body Params

Pass in a JSON body with applicant details in this format:

{
"id": "12345",
"get_person_token": True,
"partner_attributes": {
"sponsored": false,
},
"applicant_status": "New",
"applicant": {
"email": "[email protected]",
"fullName": "John Doe",
"phoneNumber": "123-456-7890",
"source": "linkedin",
"utm_source": "linkedin",
"utm_medium": "social",
"utm_campaign": "spring_campaign",
"resume": {
"file": {
"fileName": "resume.pdf",
"contentType": "application/pdf",
"data": "base64_encoded_resume_content",
}
},
},
"appliedOnMillis": 1672531199000,
"job": {
"jobId": "67890"
},
"questions": {
"answers": [
{
"id": "1",
"value": "Yes"
},
{
"id": "2",
"values": ["Option1", "Option2"]
}
]
},
"interview": {
"date": "2024-10-22T14:00:00Z",
"duration": "60",
"timezone": "America/New_York",
"location": "Virtual",
"interviewerId": "123"
}
}

The JSON document schema to send an application

applicant
object
required

Contains the applicant information

string
Defaults to New

This field is used to set the status of the candidate's application upon creation. The value of this field is expected to be a string which corresponds to a valid application status in the Apploi platform found in the Application Stage Manager page of the Apploi platform in which custom application statuses can be created. The value of this field is case-sensitive: it must precisely match the name of the status in the Apploi platform. If the interview key is present in the request, the value of this field will be ignored and the status of the candidate's application will be automatically set to 'Interview Scheduled'.

examples: New, Interview Scheduled, Hired, Rejected, etc.

integer
Defaults to 1672531199000

Timestamp of the application

boolean
Defaults to true

True to get the person token to use later to obtain the basic info of the applicant.

string
Defaults to 12345

The id of the application in the external service

interview
object

An interview information object used to schedule an interview when the application is created. If this field is present in the request, the status of the candidate's application will be automatically set to 'Interview Scheduled'. This will override the value of the applicant_status field if it is also present in the request.

job
object
required

Job information object

partner_attributes
object
required

Metadata related to the job and the origin of the application.

questions
object
Responses

409

Duplicate application: We define duplicate as an application from the same job seeker email, to the same jobid

410

If a job post is expired or no longer available.

503

request took longer than 30 seconds to complete.

Language
Credentials
Header
LoadingLoading…
Response
Click Try It! to start a request and see the response here! Or choose an example:
application/json