/applicants/{applicant_id}/interviews

Endpoint for scheduling or cancelling interviews for candidates. This endpoint allows the creation of interview appointments for a specific applicant and job. If the API is called again for the same candidate and job combination, the existing interview details will be updated with the newly provided information, effectively acting as an update operation. It cancels the latest created upcoming interview when the cancel flag is set to true. When cancelling, all other fields in the request body are ignored.

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

This endpoint schedules, reschedules or cancels an interview for a job application.

The applicant_id in the path is the job application's id: the id of an entry in the /applicants response. It is not the entry's applicant_id, which identifies the person. The job is the one that application belongs to, so you don't send a job id.

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

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.

Schedule an interview

Every request needs date, duration and timezone, plus an interviewer. You can name the interviewer in either of two ways:

  • interviewerTeamId and interviewerEmail: the id of the interviewer's team and their email address, as it appears in Apploi. The examples use this form.
  • interviewerId: the interviewer's Apploi user id, if you have it. Apploi support can give it to you.

location is optional. See Writing the request body for the exact format of each field.

The examples define a save_interview function (saveInterview 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 PUT https://partners.apploi.com/applicants/265733753/interviews \
  -H "X-Api-Key: $APPLOI_API_KEY" \
  -H "Content-Type: application/json" \
  -w "\nHTTP status: %{http_code}\n" \
  -d '{
    "date": "2026-11-03T14:00:00Z",
    "duration": "45",
    "timezone": "America/New_York",
    "location": "Virtual",
    "interviewerTeamId": "345678",
    "interviewerEmail": "[email protected]"
  }'
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 save_interview(application_id, body):
    response = requests.put(
        f"https://partners.apploi.com/applicants/{application_id}/interviews",
        headers={"X-Api-Key": API_KEY},
        json=body,
        timeout=60,
    )
    if response.status_code == 200:
        try:
            return "saved", response.json()
        except ValueError:
            # Some server errors currently arrive as a 200 whose body isn't valid JSON.
            # See "A 200 whose body isn't valid JSON" below.
            return "unconfirmed", response.text
    if response.status_code == 404:
        # The application doesn't exist or, when cancelling, it has no upcoming interview.
        return "not_found", response.text
    # 400: fix the request before sending it again. 403: the application isn't on your
    # teams. 5xx: retry with backoff.
    raise RuntimeError(f"Request failed with status {response.status_code}: {response.text}")

outcome, result = save_interview(265733753, {
    "date": "2026-11-03T14:00:00Z",  # 2:00 PM in "timezone", not UTC
    "duration": "45",  # minutes, as a string
    "timezone": "America/New_York",
    "location": "Virtual",
    "interviewerTeamId": "345678",  # a string, not a number
    "interviewerEmail": "[email protected]",
})
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 = "saved" | "unconfirmed" | "not_found";

async function saveInterview(
  applicationId: number,
  body: object,
): Promise<{ outcome: Outcome; result: unknown }> {
  const response = await fetch(
    `https://partners.apploi.com/applicants/${applicationId}/interviews`,
    {
      method: "PUT",
      headers: { "X-Api-Key": API_KEY, "Content-Type": "application/json" },
      body: JSON.stringify(body),
    },
  );
  if (response.status === 200) {
    const text = await response.text();
    try {
      return { outcome: "saved", result: JSON.parse(text) };
    } catch {
      // Some server errors currently arrive as a 200 whose body isn't valid JSON.
      // See "A 200 whose body isn't valid JSON" below.
      return { outcome: "unconfirmed", result: text };
    }
  }
  if (response.status === 404) {
    // The application doesn't exist or, when cancelling, it has no upcoming interview.
    return { outcome: "not_found", result: await response.text() };
  }
  // 400: fix the request before sending it again. 403: the application isn't on your
  // teams. 5xx: retry with backoff.
  throw new Error(`Request failed with status ${response.status}: ${await response.text()}`);
}

const { outcome, result } = await saveInterview(265733753, {
  date: "2026-11-03T14:00:00Z", // 2:00 PM in "timezone", not UTC
  duration: "45", // minutes, as a string
  timezone: "America/New_York",
  location: "Virtual",
  interviewerTeamId: "345678", // a string, not a number
  interviewerEmail: "[email protected]",
});
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 save_interview(application_id, body)
  uri = URI("https://partners.apploi.com/applicants/#{application_id}/interviews")
  request = Net::HTTP::Put.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"
    begin
      [:saved, JSON.parse(response.body)]
    rescue JSON::ParserError
      # Some server errors currently arrive as a 200 whose body isn't valid JSON.
      # See "A 200 whose body isn't valid JSON" below.
      [:unconfirmed, response.body]
    end
  # The application doesn't exist or, when cancelling, it has no upcoming interview.
  when "404" then [:not_found, response.body]
  # 400: fix the request before sending it again. 403: the application isn't on your
  # teams. 5xx: retry with backoff.
  else raise "Request failed with status #{response.code}: #{response.body}"
  end
end

outcome, result = save_interview(
  265733753,
  date: "2026-11-03T14:00:00Z", # 2:00 PM in "timezone", not UTC
  duration: "45", # minutes, as a string
  timezone: "America/New_York",
  location: "Virtual",
  interviewerTeamId: "345678", # a string, not a number
  interviewerEmail: "[email protected]"
)
puts outcome, result

Reschedule an interview

To change an interview, send the same request for the same application with the new details. Send the whole body again, not only the fields that changed. If the application's latest interview is still upcoming, it is updated, not duplicated.

If the latest interview has already taken place, the request schedules a new interview instead, and the past interview stays in the applicant's history. This is how you book a second-round interview.

These examples reuse save_interview (Python and Ruby) and saveInterview (TypeScript) from the first example. They move the interview to 10:30 AM on 5 November and shorten it to 30 minutes.

curl -sS -X PUT https://partners.apploi.com/applicants/265733753/interviews \
  -H "X-Api-Key: $APPLOI_API_KEY" \
  -H "Content-Type: application/json" \
  -w "\nHTTP status: %{http_code}\n" \
  -d '{
    "date": "2026-11-05T10:30:00Z",
    "duration": "30",
    "timezone": "America/New_York",
    "location": "Virtual",
    "interviewerTeamId": "345678",
    "interviewerEmail": "[email protected]"
  }'
outcome, _result = save_interview(265733753, {
    "date": "2026-11-05T10:30:00Z",
    "duration": "30",
    "timezone": "America/New_York",
    "location": "Virtual",
    "interviewerTeamId": "345678",
    "interviewerEmail": "[email protected]",
})
print(outcome)
const { outcome: rescheduled } = await saveInterview(265733753, {
  date: "2026-11-05T10:30:00Z",
  duration: "30",
  timezone: "America/New_York",
  location: "Virtual",
  interviewerTeamId: "345678",
  interviewerEmail: "[email protected]",
});
console.log(rescheduled);
outcome, _result = save_interview(
  265733753,
  date: "2026-11-05T10:30:00Z",
  duration: "30",
  timezone: "America/New_York",
  location: "Virtual",
  interviewerTeamId: "345678",
  interviewerEmail: "[email protected]"
)
puts outcome

Cancel an interview

Set cancel to true to cancel the application's latest upcoming interview. Apploi ignores the other fields' values when cancelling, but the request is still checked before that happens. You must still send date, duration, timezone and an interviewer, in the same formats as when scheduling. A cancel request without them returns 400. Resending the interview's current details is the simplest option.

A cancel returns 404 when the application has no upcoming interview, because none was booked, it has already taken place, or it was already cancelled. That's also what a repeated cancel returns, so a 404 after a retry means the first cancel worked. The examples use this to confirm a cancel whose response they can't rely on (see A 200 whose body isn't valid JSON).

curl -sS -X PUT https://partners.apploi.com/applicants/265733753/interviews \
  -H "X-Api-Key: $APPLOI_API_KEY" \
  -H "Content-Type: application/json" \
  -w "\nHTTP status: %{http_code}\n" \
  -d '{
    "cancel": true,
    "date": "2026-11-05T10:30:00Z",
    "duration": "30",
    "timezone": "America/New_York",
    "interviewerTeamId": "345678",
    "interviewerEmail": "[email protected]"
  }'
cancel_body = {
    "cancel": True,
    # Ignored when cancelling, but still required
    "date": "2026-11-05T10:30:00Z",
    "duration": "30",
    "timezone": "America/New_York",
    "interviewerTeamId": "345678",
    "interviewerEmail": "[email protected]",
}
outcome, _result = save_interview(265733753, cancel_body)
if outcome == "unconfirmed":
    # Send the cancel again: a 404 now means no upcoming interview is left.
    outcome, _result = save_interview(265733753, cancel_body)
if outcome == "not_found":
    print("No upcoming interview is left on this application.")
const cancelBody = {
  cancel: true,
  // Ignored when cancelling, but still required
  date: "2026-11-05T10:30:00Z",
  duration: "30",
  timezone: "America/New_York",
  interviewerTeamId: "345678",
  interviewerEmail: "[email protected]",
};
let { outcome: cancelled } = await saveInterview(265733753, cancelBody);
if (cancelled === "unconfirmed") {
  // Send the cancel again: a 404 now means no upcoming interview is left.
  ({ outcome: cancelled } = await saveInterview(265733753, cancelBody));
}
if (cancelled === "not_found") {
  console.log("No upcoming interview is left on this application.");
}
cancel_body = {
  cancel: true,
  # Ignored when cancelling, but still required
  date: "2026-11-05T10:30:00Z",
  duration: "30",
  timezone: "America/New_York",
  interviewerTeamId: "345678",
  interviewerEmail: "[email protected]"
}
outcome, _result = save_interview(265733753, cancel_body)
# Send the cancel again: a 404 now means no upcoming interview is left.
outcome, _result = save_interview(265733753, cancel_body) if outcome == :unconfirmed
puts "No upcoming interview is left on this application." if outcome == :not_found

Writing the request body

Most rejected requests come from the field formats below. Send every value as a JSON string, even the numbers. Only cancel is a boolean.

FieldRequiredFormatExample
dateYesISO 8601 date and time, ending in Z. See the note below the table."2026-11-03T14:00:00Z"
durationYesLength in minutes, as a string"45"
timezoneYesAn IANA time zone name"America/New_York"
interviewerTeamId and interviewerEmailEither these two, or interviewerIdThe interviewer's team id, as a string, and their email address in Apploi. The email must belong to a member of that team."345678", "[email protected]"
interviewerIdThe interviewer's Apploi user id, as a string"123456"
locationNoAny text, such as an address, "Virtual" or a meeting link, but without double quotes ("), backslashes (\) or line breaks, which make the request fail with 400"Virtual"
cancelNotrue to cancel. See Cancel an interview.true

How date and timezone work together. date holds the interview's local time, and timezone says where that time applies. Apploi ignores the offset in date, so "2026-11-03T14:00:00Z" with "America/New_York" means 2:00 PM in New York, not 2:00 PM UTC. Don't convert the time to UTC first: an interview at 2:00 PM in New York is sent as 14:00:00Z, not 19:00:00Z. The offset is still required, so a date with no Z returns 400.

What the response means

Decide what happened from the status code, not from the body. The one exception for now is a 200 whose body isn't valid JSON, described below the table.

StatusMeaningWhat to do
200The interview was scheduled, rescheduled or cancelled. Apploi also moves the application to the matching status, such as Interview Scheduled, if the team has one.Nothing, unless the body isn't valid JSON.
400The request is invalid. The usual causes are a number where a string is expected, a missing required field (cancel requests included), a date with no offset, or an interviewerEmail that doesn't belong to interviewerTeamId's team.Fix the request before sending it again. An unchanged retry fails the same way.
403The API key is missing or invalid, the application doesn't belong to one of your teams, or your API key isn't allowed to manage interviews.Check the key and the id in the path. If both are right, contact Apploi support.
404The application doesn't exist. Check that the path has the application's id, not its applicant_id. For a cancel, it can also mean there is no upcoming interview.Don't retry with the same id.
5xxSomething failed on Apploi's side, or the request took longer than 29 seconds.Wait, then retry with exponential backoff. If you send interviewerId and every retry returns 500, the id is probably not an Apploi user. Check it rather than retrying.

Retrying an interview that is still upcoming is safe. A retry updates the same interview rather than adding a second one, and a repeated cancel returns 404. If the first attempt did go through, the retry counts as a reschedule, so the application's status can read Interview Rescheduled rather than Interview Scheduled. Don't retry a request whose date is in the past: each attempt adds another interview.

A 200 whose body isn't valid JSON

Some server errors, for example during an Apploi deployment, currently reach you as a 200 whose body isn't valid JSON, not as a 5xx. The examples report these as unconfirmed.

  • After a schedule or reschedule, a real 200 always has a valid JSON body. Treat any other 200 as a 5xx: wait, then retry.
  • After a cancel, a real 200 can also have a body that isn't valid JSON, so the body doesn't tell you whether the cancel worked. Send the cancel again. A 404 confirms that no upcoming interview is left.

Response bodies

A 400 for a wrong field type or a missing field comes back with no detail:

{
  "message": "Invalid request body"
}

A request with no interviewer names the problem:

{
  "error": "Validation Error",
  "message": "Either 'interviewerId' must be provided, or both 'interviewerTeamId' and 'interviewerEmail' must be provided.",
  "details": {
    "validationErrors": [
      "Either 'interviewerId' must be provided, or both 'interviewerTeamId' and 'interviewerEmail' must be provided."
    ]
  }
}

Other 400 responses, such as an interviewerEmail that isn't on the team or a date with no offset, carry the reason inside validationErrors:

{
  "error": "Bad Request",
  "message": "Invalid request body",
  "validationErrors": {
    "developer_message": "There were one or more field errors",
    "errors": {
      "message": "No user found with the provided interviewer_email and team_id."
    }
  }
}

After a schedule or reschedule, a 200 response describes the application and an interview. id is the application's id, the one in the path, and applicant_id is the person's id.

{
  "data": {
    "id": 265733753,
    "applicant_id": 71207841,
    "applicant_status": "Interview Scheduled",
    "applicant_status_id": 8,
    "date": "2026-11-03T14:00:00+00:00",
    "duration": 45,
    "location": "Virtual",
    "timezone": "America/New_York",
    "interviewer": {
      "id": 123456,
      "full_name": "Jane Doe"
    },
    "job_id": 1470975
  }
}

The response's date ends in +00:00, but like the request it is the local time in timezone.

Don't use the interview fields in the body to confirm what was saved. When the application has more than one interview, for example after a second round, they can describe an earlier interview. After a cancel, don't read the body at all: it describes whatever interview is left, or has empty interview fields and may not be valid JSON. Confirm a cancel as described in A 200 whose body isn't valid JSON.

Path Params
string
required

The application form ID corresponding to a job application in the Apploi platform. The application form ID is a unique identifier for the job application in the Apploi platform. The application form ID can be found in the 'id' field of the each of the items in the response body of a call to the /applicants endpoint in the Apploi Partner API.

Examples: 265733753, 265733754, etc.

Body Params

Pass in a JSON body with interview details. Either interviewerId must be provided, or both interviewerTeamId and interviewerEmail must be provided.

Option 1: Using interviewerId

{
    "date": "2022-12-05T17:30:00Z",
    "duration": "60",
    "timezone": "America/New_York",
    "location": "Virtual",
    "interviewerId": "123456"
}

Option 2: Using interviewerTeamId and interviewerEmail

{
    "date": "2022-12-05T17:30:00Z",
    "duration": "60",
    "timezone": "America/New_York",
    "location": "Virtual",
    "interviewerEmail": "[email protected]",
    "interviewerTeamId": "23567"
}

Option 3: Cancelling the latest interview

Note: Scheduling fields are still required by the schema but will be ignored by the service when cancel is true.

{
    "date": "2022-12-05T17:30:00Z",
    "duration": "60",
    "timezone": "America/New_York",
    "interviewerId": "123456",
    "cancel": true
}

The JSON document schema for scheduling an interview for a specific candidate for a specific instance of a job application.

boolean

When set to true, cancels the latest created upcoming interview for the specified applicant. All other fields in the request body are ignored when this flag is true.

Defaults to false.

date-time
required

Interview date and time, specified in ISO 8601 format. Non-ISO 8601 format date-time strings are not allowed. The timezone information provided in this value will be overridden by the value provided in the timezone field. However, this field still requires timezone information to be present, so simply appending Z at the end of the date-time string is sufficient.

Examples: "2021-03-25T10:00:00Z", "2024-10-22T14:00:00Z", etc.

string
required
Defaults to 60

The interview duration in minutes. The type of this field is a numeric integer string. Non-numeric string characters are not allowed.

Examples: "30", "60", "90", etc.

string
Defaults to [email protected]

The email address of the interviewer in the Apploi platform. This field is only required when the interviewerId is not available. This must be a valid email address string of a team member recorded in the Apploi platform.

Examples: "[email protected]", "[email protected]", etc.

string
Defaults to 123456

The User ID of the interviewer in the Apploi platform. This field must be an integer string value. Non-integer string characters are not allowed. This is the user ID of a team member recorded in the Apploi platform. Reaching out to the Apploi support team may be the best way to get the correct user ID for the interviewer.

Examples: "123456", "789012", etc.

string
Defaults to 345678

The Team ID of the interviewer in the Apploi platform. This field is only required when the interviewerId is not available. This field must be an integer string value. Non-integer string characters are not allowed. This is the team ID of a team member recorded in the Apploi platform. Reaching out to the Apploi support team may be the best way to get the correct team ID for the interviewer.

Examples: "345678", "987898", etc.

string
Defaults to Virtual

The location of the interview. The only restriction on this field is that it must be a string. This allows for specifying a physical location, a virtual location, or any other type of location.

Examples: "123 Main St, New York, NY 10001", "Virtual", "https://meet.google.com/abc-123-xyz", etc.

string
required
Defaults to America/New_York

The timezone of the interview date and time. The timezone must be a valid timezone string as defined by the IANA Time Zone Database. This timezone will override any timezone information specified in the date field.

Examples: "America/New_York", "Europe/London", "Asia/Tokyo", etc.

Responses

403

Invalid Token

404

Not Found

405

405 response

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