/applicants

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

Here is the documentation to use this endpoint as library.

For the full workflow around job applications (finding them, updating their status, messaging applicants and scheduling interviews), see the Applicant & Status Management guide.

Examples

Each example reads the job applications for one team. Replace 123456 with one of your team ids.

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.

Your first request

Ask for one small page of job applications:

# 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 \
  -H "X-Api-Key: $APPLOI_API_KEY" \
  --data-urlencode "team_id=123456" \
  --data-urlencode "limit=50"
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"]

response = requests.get(
    "https://partners.apploi.com/applicants",
    headers={"X-Api-Key": API_KEY},
    params={"team_id": 123456, "limit": 50},
    timeout=60,
)
response.raise_for_status()
page = response.json()

for job_application in page["data"]:
    print(job_application["id"], job_application["job_id"], job_application["applicant_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;

const params = new URLSearchParams({ team_id: "123456", limit: "50" });
const response = await fetch(
  `https://partners.apploi.com/applicants?${params}`,
  {
    headers: { "X-Api-Key": API_KEY },
  },
);
if (!response.ok) {
  throw new Error(`Request failed with status ${response.status}`);
}
const page = await response.json();

for (const jobApplication of page.data) {
  console.log(
    jobApplication.id,
    jobApplication.job_id,
    jobApplication.applicant_updated,
  );
}
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")

uri = URI("https://partners.apploi.com/applicants")
uri.query = URI.encode_www_form(team_id: 123456, limit: 50)
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)
page = JSON.parse(response.body)

page["data"].each do |job_application|
  puts [job_application["id"], job_application["job_id"], job_application["applicant_updated"]].join(" ")
end

The response wraps the job applications in data and echoes the offset and limit that were applied. Trimmed to a few fields, it looks like this:

{
  "data": [
    {
      "id": 98765432,
      "applicant_id": 1234567,
      "job_id": 7654321,
      "applicant_updated": "2026-09-18T14:03:22+00:00"
    }
  ],
  "offset": 0,
  "limit": 50
}

Each entry is one job application: one person's application to one job. id identifies the job application and applicant_id identifies the person who applied, so a person who applied to two jobs appears twice with the same applicant_id.

Paging through job applications

Results come back with the most recent job applications first. They are ordered by application date, not by applicant_updated. To read more than one page, add limit to offset on each request, and stop when a page holds fewer job applications than the limit in the response.

Compare against the response's limit rather than the one you sent. The largest page is 100, and a larger limit is served as 100 rather than rejected, so a loop that compares against a requested 500 would stop after the first page. When that happens the response also carries a warning naming the maximum. Don't rely on a total field to know when to stop either: it isn't always present.

The results can change while you page:

  • When a job application joins the results, later rows move down one place, so one row can appear on two pages. The examples keep one copy of each job application by id.
  • When a job application leaves the results, later rows move up one place, so one row can be skipped.

To catch anything skipped, run a full read from time to time as well as the incremental reads described below.

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

# Page 1
curl -G https://partners.apploi.com/applicants \
  -H "X-Api-Key: $APPLOI_API_KEY" \
  --data-urlencode "team_id=123456" \
  --data-urlencode "limit=50" \
  --data-urlencode "offset=0"

# Page 2: offset increases by limit
curl -G https://partners.apploi.com/applicants \
  -H "X-Api-Key: $APPLOI_API_KEY" \
  --data-urlencode "team_id=123456" \
  --data-urlencode "limit=50" \
  --data-urlencode "offset=50"
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 fetch_all_job_applications(team_id, params=None):
    job_applications = {}
    offset = 0
    while True:
        response = requests.get(
            "https://partners.apploi.com/applicants",
            headers={"X-Api-Key": API_KEY},
            params={"team_id": team_id, "limit": 50, "offset": offset, **(params or {})},
            timeout=60,
        )
        response.raise_for_status()
        page = response.json()

        for job_application in page["data"]:
            job_applications[job_application["id"]] = job_application

        if len(page["data"]) < page["limit"]:
            return list(job_applications.values())
        offset += page["limit"]

job_applications = fetch_all_job_applications(123456)
// 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 fetchAllJobApplications(
  teamId: string,
  extraParams: Record<string, string> = {},
): Promise<any[]> {
  const jobApplications = new Map<number, any>();
  let offset = 0;
  while (true) {
    const params = new URLSearchParams({
      team_id: teamId,
      limit: "50",
      offset: String(offset),
      ...extraParams,
    });
    const response = await fetch(
      `https://partners.apploi.com/applicants?${params}`,
      {
        headers: { "X-Api-Key": API_KEY },
      },
    );
    if (!response.ok) {
      throw new Error(`Request failed with status ${response.status}`);
    }
    const page = await response.json();

    for (const jobApplication of page.data) {
      jobApplications.set(jobApplication.id, jobApplication);
    }

    if (page.data.length < page.limit) {
      return [...jobApplications.values()];
    }
    offset += page.limit;
  }
}

const jobApplications = await fetchAllJobApplications("123456");
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 fetch_all_job_applications(team_id, params = {})
  job_applications = {}
  offset = 0
  loop do
    uri = URI("https://partners.apploi.com/applicants")
    uri.query = URI.encode_www_form({ team_id: team_id, limit: 50, offset: offset }.merge(params))
    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)
    page = JSON.parse(response.body)

    page["data"].each do |job_application|
      job_applications[job_application["id"]] = job_application
    end

    return job_applications.values if page["data"].length < page["limit"]

    offset += page["limit"]
  end
end

job_applications = fetch_all_job_applications(123456)

Pulling only what changed

After the first full read, sync on a schedule and ask only for job applications updated since your last successful run with updated_after. This is much faster than reading everything again.

Send times in UTC. A timezone offset is accepted but not applied, so 10:00:00-05:00 is read as 10:00:00Z. Record the time a run starts, minus a few minutes, and save it as the next run's updated_after only after the run finishes without errors. Anything updated while the run was in progress is then picked up next time. The few minutes cover the case where your clock is ahead of Apploi's: without them, job applications updated in that gap would never be returned. The filter includes its boundary, so a few job applications may come back twice across runs. Update them by id rather than inserting them again.

These examples reuse fetch_all_job_applications (Python and Ruby) / fetchAllJobApplications (TypeScript) from the paging 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 -G https://partners.apploi.com/applicants \
  -H "X-Api-Key: $APPLOI_API_KEY" \
  --data-urlencode "team_id=123456" \
  --data-urlencode "limit=50" \
  --data-urlencode "offset=0" \
  --data-urlencode "updated_after=2026-09-01T00:00:00Z"
from datetime import datetime, timedelta, timezone

def sync_changed_job_applications(team_id, last_run_started):
    # A few minutes earlier than now, in case this clock is ahead of Apploi's
    run_started = (datetime.now(timezone.utc) - timedelta(minutes=5)).strftime(
        "%Y-%m-%dT%H:%M:%SZ"
    )
    changed_job_applications = fetch_all_job_applications(
        team_id, {"updated_after": last_run_started}
    )
    # Store the changed job applications, updating any you already have by "id"...
    return run_started  # save this as last_run_started for the next run

next_checkpoint = sync_changed_job_applications(123456, "2026-09-01T00:00:00Z")
async function syncChangedJobApplications(
  teamId: string,
  lastRunStarted: string,
): Promise<string> {
  // A few minutes earlier than now, in case this clock is ahead of Apploi's
  const runStarted = new Date(Date.now() - 5 * 60 * 1000)
    .toISOString()
    .replace(/\.\d{3}Z$/, "Z");
  const changedJobApplications = await fetchAllJobApplications(teamId, {
    updated_after: lastRunStarted,
  });
  // Store the changed job applications, updating any you already have by "id"...
  return runStarted; // save this as lastRunStarted for the next run
}

const nextCheckpoint = await syncChangedJobApplications(
  "123456",
  "2026-09-01T00:00:00Z",
);
def sync_changed_job_applications(team_id, last_run_started)
  # A few minutes earlier than now, in case this clock is ahead of Apploi's
  run_started = (Time.now.utc - 5 * 60).strftime("%Y-%m-%dT%H:%M:%SZ")
  changed_job_applications = fetch_all_job_applications(team_id, { updated_after: last_run_started })
  # Store the changed job applications, updating any you already have by "id"...
  run_started # save this as last_run_started for the next run
end

next_checkpoint = sync_changed_job_applications(123456, "2026-09-01T00:00:00Z")

Keeping requests fast

A request that takes more than 29 seconds is cut off with a 504 error. Large, unfiltered reads are the usual cause. To stay well inside the limit:

  • Always send team_id, and add job_id when you only need one job.
  • Use a page size of around 50 rather than the maximum.
  • Use updated_after for regular syncs instead of reading every job application each time.
  • If a request returns 504 or another 5xx error, wait a few seconds and retry the same page.
Query Params
string
string
string

The maximum number of job applications to return in a single response. Defaults to 30. The maximum is 100: a limit above 100, or one that is not a whole number, is not rejected — it is served as 100, and the response carries a warning saying so. A limit of 0 or less returns 400. The response's limit is the page size that was applied. Use offset to page through large result sets.

string
string

Filter to include only job applications that were updated before the given date. By using the updated_after and updated_before filters together, entries returned in the response can be filtered by a window of time in which they were most recently updated.
The values provided in the filter must be in one of the following formats:

• Dates in the format of YYYY-MM-DD
    • examples: `2024-10-29`, `2017-09-28`, `2020-01-01`
• Date-times in isoformat, in UTC.
    • examples: `2024-10-29T10:00:00`, `2024-10-29T10:00:00Z`, `2024-10-29T10:00:00+00:00`

Every value is read as UTC. A timezone offset is accepted but not applied, so 2024-10-29T10:00:00-05:00 is read as 2024-10-29T10:00:00Z; convert to UTC before sending. A date on its own means midnight UTC at the start of that day. Both filters include the boundary: updated_after returns job applications updated at or after the given time, and updated_before at or before it.

string

The number of job applications to skip before returning results, used for pagination. Defaults to 0. To page through all job applications, increment offset by the response's limit on each request (e.g. limit=50&offset=0, then limit=50&offset=50, ...) until a page returns fewer results than the limit in the response. Compare against the response's limit rather than the one you sent, because a limit above 100 is served as 100.

string
string

Filter to include only job applications that were updated after the given date. By using the updated_after and updated_before filters together, entries returned in the response can be filtered by a window of time in which they were most recently updated.
The values provided in the filter must be in one of the following formats:

• Dates in the format of YYYY-MM-DD
    • examples: `2024-10-29`, `2017-09-28`, `2020-01-01`
• Date-times in isoformat, in UTC.
    • examples: `2024-10-29T10:00:00`, `2024-10-29T10:00:00Z`, `2024-10-29T10:00:00+00:00`

Every value is read as UTC. A timezone offset is accepted but not applied, so 2024-10-29T10:00:00-05:00 is read as 2024-10-29T10:00:00Z; convert to UTC before sending. A date on its own means midnight UTC at the start of that day. Both filters include the boundary: updated_after returns job applications updated at or after the given time, and updated_before at or before it.

Headers
string
required
Responses

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