This endpoint is used to update the status of job applications. It can be used to update the status of a single application or multiple applications at once, depending on the request body.
| Time | Status | User Agent | |
|---|---|---|---|
Retrieving recent requests… | |||
This endpoint moves one or more job applications to a new status, such as Interview Scheduled or Hired. Each entry returned by /applicants is one job application: one person's application to one job. Its id is the value to send in application_ids.
The request body has two fields:
application_ids: an array of job application ids, as integers. Send an array even for a single job application.status: the name of the status, for example"Interview Scheduled". It must match one of the names returned by /applicants/applicant-statuses for the team that owns the job.
For the full workflow around job applications (finding them, updating their status, messaging applicants and scheduling interviews), see the Applicant & Status Management guide.
Examples
Updating a status takes two steps. First, look up the status names that the job's team uses. Then send the update.
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.
Step 1: Look up the team's status names
Each team has a list of statuses, including any custom statuses added in the Application Stage Manager. A team that hasn't set up its own statuses uses its parent team's list. A status is only valid for job applications to jobs of a team that has it in its list.
The team_id to look up is the team that owns the job. Every job application returned by /applicants includes it as job.team_id. Job applications to jobs in the same team share one list of statuses, so look it up once per team and reuse it.
# 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 -G https://partners.apploi.com/applicants/applicant-statuses \
-H "X-Api-Key: $APPLOI_API_KEY" \
--data-urlencode "team_id=123456"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 get_status_names(team_id):
response = requests.get(
"https://partners.apploi.com/applicants/applicant-statuses",
headers={"X-Api-Key": API_KEY},
params={"team_id": team_id},
timeout=60,
)
response.raise_for_status()
return [status["name"] for status in response.json()["data"]]
status_names = get_status_names(123456)
print(status_names)// 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;
async function getStatusNames(teamId: string): Promise<string[]> {
const params = new URLSearchParams({ team_id: teamId });
const response = await fetch(
`https://partners.apploi.com/applicants/applicant-statuses?${params}`,
{ headers: { "X-Api-Key": API_KEY } },
);
if (!response.ok) {
throw new Error(`Request failed with status ${response.status}`);
}
const body = await response.json();
return body.data.map((status: { name: string }) => status.name);
}
const statusNames = await getStatusNames("123456");
console.log(statusNames);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 get_status_names(team_id)
uri = URI("https://partners.apploi.com/applicants/applicant-statuses")
uri.query = URI.encode_www_form(team_id: team_id)
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: 60) do |http|
http.request(request)
end
raise "Request failed with status #{response.code}" unless response.is_a?(Net::HTTPSuccess)
JSON.parse(response.body)["data"].map { |status| status["name"] }
end
status_names = get_status_names(123456)
puts status_namesTrimmed to a few fields, the response looks like this. Every value in it is a string, including id and team_id. If the team uses its parent team's list, team_id is the parent team's id rather than the one you asked for:
{
"data": [
{ "id": "28356", "name": "New", "team_id": "123456" },
{ "id": "28357", "name": "Interview Scheduled", "team_id": "123456" },
{ "id": "28358", "name": "Waiting for Background Check", "team_id": "123456" },
{ "id": "28359", "name": "Hired", "team_id": "123456" }
]
}Send the name in your update, not the id. Copy it exactly as returned.
Step 2: Update one job application
Send the job application's id from /applicants in application_ids, as an integer inside an array, and the status name in status.
The examples define an update_status function (updateStatus in TypeScript), which the next example reuses. It returns the ids that were updated and the ids that weren't, with a reason for each. What the response means explains how it decides.
# 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 PUT https://partners.apploi.com/applicants/status \
-H "X-Api-Key: $APPLOI_API_KEY" \
-H "Content-Type: application/json" \
-w "\nHTTP status: %{http_code}\n" \
-d '{
"application_ids": [98765432],
"status": "Interview Scheduled"
}'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 update_status(application_ids, status):
response = requests.put(
"https://partners.apploi.com/applicants/status",
headers={"X-Api-Key": API_KEY},
json={"application_ids": application_ids, "status": status},
timeout=60,
)
if response.status_code == 400:
# The request was rejected. Fix it before sending it again.
raise ValueError(f"Status update rejected: {response.text}")
# Any other error (403, 5xx): see "What the response means" before retrying.
response.raise_for_status()
data = response.json()["data"]
returned_ids = {application["id"] for application in data["list_application_form"]}
updated, not_updated = [], {}
for application_id in application_ids:
if application_id not in returned_ids:
not_updated[application_id] = "No job application with this id"
else:
updated.append(application_id)
return updated, not_updated
updated, not_updated = update_status([98765432], "Interview Scheduled")
print("Updated:", updated)
print("Not updated:", not_updated)// 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;
async function updateStatus(
applicationIds: number[],
status: string,
): Promise<{ updated: number[]; notUpdated: Map<number, string> }> {
const response = await fetch("https://partners.apploi.com/applicants/status", {
method: "PUT",
headers: { "X-Api-Key": API_KEY, "Content-Type": "application/json" },
body: JSON.stringify({ application_ids: applicationIds, status }),
});
if (response.status === 400) {
// The request was rejected. Fix it before sending it again.
throw new Error(`Status update rejected: ${await response.text()}`);
}
if (!response.ok) {
// Any other error (403, 5xx): see "What the response means" before retrying.
throw new Error(`Request failed with status ${response.status}`);
}
const { data } = await response.json();
const returnedIds = new Set<number>(
data.list_application_form.map((application: { id: number }) => application.id),
);
const updated: number[] = [];
const notUpdated = new Map<number, string>();
for (const applicationId of applicationIds) {
if (!returnedIds.has(applicationId)) {
notUpdated.set(applicationId, "No job application with this id");
} else {
updated.push(applicationId);
}
}
return { updated, notUpdated };
}
const { updated, notUpdated } = await updateStatus([98765432], "Interview Scheduled");
console.log("Updated:", updated);
console.log("Not updated:", notUpdated);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 update_status(application_ids, status)
uri = URI("https://partners.apploi.com/applicants/status")
request = Net::HTTP::Put.new(uri)
request["X-Api-Key"] = API_KEY
request["Content-Type"] = "application/json"
request.body = JSON.generate({ application_ids: application_ids, status: status })
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true, read_timeout: 60) do |http|
http.request(request)
end
# The request was rejected. Fix it before sending it again.
raise ArgumentError, "Status update rejected: #{response.body}" if response.code == "400"
# Any other error (403, 5xx): see "What the response means" before retrying.
raise "Request failed with status #{response.code}" unless response.is_a?(Net::HTTPSuccess)
data = JSON.parse(response.body)["data"]
returned_ids = data["list_application_form"].map { |application| application["id"] }
updated = []
not_updated = {}
application_ids.each do |application_id|
if !returned_ids.include?(application_id)
not_updated[application_id] = "No job application with this id"
else
updated << application_id
end
end
[updated, not_updated]
end
updated, not_updated = update_status([98765432], "Interview Scheduled")
puts "Updated: #{updated}"
puts "Not updated: #{not_updated}"Update several job applications at once
To move several job applications to the same status, send all of their ids in one request. To move job applications to different statuses, send one request per status.
The status name has to exist for the team of every job in the request. If one of the jobs belongs to a team that doesn't have that status, the whole request returns 400. The simplest way to avoid this is to group job applications by job.team_id and send one request per team.
Each job application in a request is processed in turn, so keep batches modest, for example 50 ids or fewer. Very large batches can run past the 29-second limit described in What the response means.
These examples reuse update_status (Python and Ruby) and updateStatus (TypeScript) from Step 2.
# 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 PUT https://partners.apploi.com/applicants/status \
-H "X-Api-Key: $APPLOI_API_KEY" \
-H "Content-Type: application/json" \
-w "\nHTTP status: %{http_code}\n" \
-d '{
"application_ids": [98765432, 98765433, 98765434],
"status": "Waiting for Background Check"
}'updated, not_updated = update_status(
[98765432, 98765433, 98765434], "Waiting for Background Check"
)
for application_id, reason in not_updated.items():
print(f"{application_id} was not updated: {reason}")const batch = await updateStatus(
[98765432, 98765433, 98765434],
"Waiting for Background Check",
);
for (const [applicationId, reason] of batch.notUpdated) {
console.log(`${applicationId} was not updated: ${reason}`);
}updated, not_updated = update_status([98765432, 98765433, 98765434], "Waiting for Background Check")
not_updated.each do |application_id, reason|
puts "#{application_id} was not updated: #{reason}"
endWhat the response means
200: the request was accepted. A 200 doesn't mean every job application was updated, so check each id you sent. Trimmed to a few fields, the response looks like this:
{
"data": {
"list_application_form": [
{
"id": 98765432,
"applicant_id": 1234567,
"job_id": 7654321,
"applicant_state": "Waiting for Background Check",
"applicant_state_id": 28358,
"date_current_state_assigned": "2026-09-18T14:03:22+00:00"
},
{
"id": 98765433,
"applicant_id": 1234568,
"job_id": 7654321,
"applicant_state": "Waiting for Background Check",
"applicant_state_id": 28358,
"date_current_state_assigned": "2026-09-18T14:03:22+00:00"
}
]
}
}In this example, the three ids sent had two different outcomes:
- Updated:
98765432and98765433are inlist_application_form, andapplicant_stateshows their new status. - Not found:
98765434is missing from the response. Ids that don't match a job application are left out without an error, so compare the ids you sent with the ids returned. Check that the id is the job application'sidfrom /applicants, not itsapplicant_idorjob_id. If none of the ids match, the response is a200with an emptylist_application_form, even when the status name is wrong.
400: validation failure. The request was rejected. Fix it before sending it again, because sending it unchanged gets the same 400. The body has a developer_message explaining the problem:
{
"developer_message": "Specified application state doesn't exist for the team."
}This message is the most common one. It means the status doesn't match a status name for the team that owns one of the jobs. To fix it:
- Look up the names again with /applicants/applicant-statuses, using the job application's
job.team_id. - Copy the
nameexactly as returned. - Make sure you're sending the
nameand not theid. - If the request mixes jobs from different teams, split it by team.
When this error is returned, none of the job applications in the request were updated, so send the corrected request with all of them. Automations for job applications processed before the error may already have started, and those can run again when the corrected request succeeds.
Other 400 responses mean the body doesn't have the expected shape, for example:
application_idsis missing.application_idsis a single number rather than an array.- The body isn't valid JSON.
403: forbidden. The X-Api-Key header is missing or not valid, or your API key isn't allowed to update job applications. Contact your Apploi CSM if your key should have access.
5xx: server error. Don't resend the same request straight away. Depending on where the error happened, some or all of the job applications may already have their new status, and automations for some of them may already have started. Wait a few seconds, then check each job application's applicant_state in /applicants and retry only those that don't have the new status yet.
A request that takes more than 29 seconds is cut off with a 504 error. The update usually carries on and finishes after the 504, so the same check applies. Send smaller batches next time.
Tips
- Only send job applications whose status needs to change. Sending a job application the status it already has isn't ignored. It counts as a new status change, so any automation the team has set up for that status can run again. The
applicant_statefield in /applicants holds each job application's current status. - Look up status names once per team. Re-check them from time to time, since teams can rename or add statuses in the Application Stage Manager.
400400 response
403Invalid Token

