/applicants/{applicant_id}/message

Creates a new message for a specific application. Messages can be sent to the candidate (outbound) or recorded as responses from the candidate (inbound). Supports both text (SMS) and email message types.

Message Direction:

  • 'to_candidate': Outbound messages sent to the candidate (e.g., from AI system)
  • 'from_candidate': Inbound messages/responses from the candidate

Message Delivery Control:

  • is_sent_by_vendor: default true if not sent in payload
  • is_sent_by_vendor=true: Vendor handles message delivery
  • is_sent_by_vendor=false: Apploi sends the message via SMS/Email infrastructure

Email Requirements: When message_type is 'email', the 'subject' field is required.

Authentication: Requires valid x-api-key header.

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

This endpoint adds a text or email message to a job application's message history in Apploi. It covers three cases:

  • recording a message you have already sent to the applicant,
  • asking Apploi to send a message to the applicant,
  • recording a reply from the applicant that you received.

To read the history back, use GET /applicants/{applicant_id}/message.

Before you start:

  • applicant_id is the job application's id, the id field of an entry returned by /applicants. It is not the applicant_id field from that response, which identifies the person.
  • Messaging must be turned on for the team. Ask your Apploi contact to enable it.
  • Every request needs a sender_email, including replies from the applicant. It must be the email address of an active Apploi user on the team that owns the job. Use the recruiter the conversation belongs to.

Who delivers the message

is_sent_by_vendor decides whether Apploi only records the message or also delivers it:

is_sent_by_vendorWhat Apploi doesAllowed directions
true (the default)Records the message in the history. You deliver it, or you already have.to_candidate and from_candidate
falseRecords the message and delivers it. A text goes from an Apploi number to the applicant's mobile number. An email goes to the applicant's email address, and replies go to the user who created the job.to_candidate only

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.

Replace 98765432 with the id of one of your job applications and [email protected] with the email address of a user on its team. Set vendor_name to your company's name.

Recording a text you sent

Send "is_sent_by_vendor": true (or leave it out) for a message you deliver yourself.

The examples define a post_message function (postMessage in TypeScript), which the later examples reuse. It checks the body as well as the status code, because some errors on this endpoint arrive with a 200 status (see 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/applicants/98765432/message \
  -H "X-Api-Key: $APPLOI_API_KEY" \
  -H "Content-Type: application/json" \
  -w "\nHTTP status: %{http_code}\n" \
  -d '{
    "external_id": "msg7f3a90",
    "message_type": "text",
    "message_direction": "to_candidate",
    "message_body": "Hi Jane, thanks for applying. Are you free for a phone screen this week?",
    "status": "DELIVERED",
    "vendor_name": "Acme Messaging",
    "is_sent_by_vendor": true,
    "sender_email": "[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 post_message(job_application_id, message):
    response = requests.post(
        f"https://partners.apploi.com/applicants/{job_application_id}/message",
        headers={"X-Api-Key": API_KEY},
        json=message,
        timeout=30,
    )
    if not response.ok:
        # 400: fix the request before sending it again. 5xx: see "Retrying safely".
        raise RuntimeError(f"Request failed with status {response.status_code}: {response.text}")
    try:
        body = response.json()
    except ValueError:
        # A server error can arrive with status 200 and a body that isn't JSON. See "Retrying safely".
        raise RuntimeError(f"Unexpected response: {response.text[:200]}")
    result = body.get("data")
    if not result or result.get("status") != "success":
        # Some errors arrive with status 200. See "What the response means".
        raise RuntimeError(f"Message not accepted: {body}")
    return result

recorded = post_message(98765432, {
    "external_id": "msg7f3a90",  # your own id, up to 36 characters
    "message_type": "text",
    "message_direction": "to_candidate",
    "message_body": "Hi Jane, thanks for applying. Are you free for a phone screen this week?",
    "status": "DELIVERED",
    "vendor_name": "Acme Messaging",
    "is_sent_by_vendor": True,
    "sender_email": "[email protected]",
})
print(recorded["message"])  # "Message updated successfully"
// 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 postMessage(jobApplicationId: number, message: object): Promise<any> {
  const response = await fetch(
    `https://partners.apploi.com/applicants/${jobApplicationId}/message`,
    {
      method: "POST",
      headers: { "X-Api-Key": API_KEY, "Content-Type": "application/json" },
      body: JSON.stringify(message),
    },
  );
  if (!response.ok) {
    // 400: fix the request before sending it again. 5xx: see "Retrying safely".
    throw new Error(`Request failed with status ${response.status}: ${await response.text()}`);
  }
  const text = await response.text();
  let body: any;
  try {
    body = JSON.parse(text);
  } catch {
    // A server error can arrive with status 200 and a body that isn't JSON. See "Retrying safely".
    throw new Error(`Unexpected response: ${text.slice(0, 200)}`);
  }
  if (body.data?.status !== "success") {
    // Some errors arrive with status 200. See "What the response means".
    throw new Error(`Message not accepted: ${JSON.stringify(body)}`);
  }
  return body.data;
}

const recorded = await postMessage(98765432, {
  external_id: "msg7f3a90", // your own id, up to 36 characters
  message_type: "text",
  message_direction: "to_candidate",
  message_body: "Hi Jane, thanks for applying. Are you free for a phone screen this week?",
  status: "DELIVERED",
  vendor_name: "Acme Messaging",
  is_sent_by_vendor: true,
  sender_email: "[email protected]",
});
console.log(recorded.message); // "Message updated successfully"
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 post_message(job_application_id, message)
  uri = URI("https://partners.apploi.com/applicants/#{job_application_id}/message")
  request = Net::HTTP::Post.new(uri)
  request["X-Api-Key"] = API_KEY
  request["Content-Type"] = "application/json"
  request.body = JSON.generate(message)

  response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true, read_timeout: 30) do |http|
    http.request(request)
  end
  # 400: fix the request before sending it again. 5xx: see "Retrying safely".
  raise "Request failed with status #{response.code}: #{response.body}" unless response.is_a?(Net::HTTPSuccess)

  body = begin
    JSON.parse(response.body)
  rescue JSON::ParserError
    # A server error can arrive with status 200 and a body that isn't JSON. See "Retrying safely".
    raise "Unexpected response: #{response.body[0, 200]}"
  end
  result = body["data"]
  # Some errors arrive with status 200. See "What the response means".
  raise "Message not accepted: #{body}" unless result.is_a?(Hash) && result["status"] == "success"

  result
end

recorded = post_message(98765432, {
  external_id: "msg7f3a90", # your own id, up to 36 characters
  message_type: "text",
  message_direction: "to_candidate",
  message_body: "Hi Jane, thanks for applying. Are you free for a phone screen this week?",
  status: "DELIVERED",
  vendor_name: "Acme Messaging",
  is_sent_by_vendor: true,
  sender_email: "[email protected]"
})
puts recorded["message"] # "Message updated successfully"

Having Apploi send an email

Send "is_sent_by_vendor": false to have Apploi deliver the message. An email needs a subject. Without one, the request fails with a 200 status (see What the response means).

Apploi sets status itself once it has queued the email, so the value you send is replaced.

# 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/applicants/98765432/message \
  -H "X-Api-Key: $APPLOI_API_KEY" \
  -H "Content-Type: application/json" \
  -w "\nHTTP status: %{http_code}\n" \
  -d '{
    "external_id": "msg7f3a91",
    "message_type": "email",
    "message_direction": "to_candidate",
    "subject": "Interview invitation",
    "message_body": "Thanks for applying. Could you come in for an interview on Tuesday at 10am?",
    "status": "QUEUED",
    "vendor_name": "Acme Messaging",
    "is_sent_by_vendor": false,
    "sender_email": "[email protected]"
  }'
sent = post_message(98765432, {
    "external_id": "msg7f3a91",
    "message_type": "email",
    "message_direction": "to_candidate",
    "subject": "Interview invitation",  # required for email
    "message_body": "Thanks for applying. Could you come in for an interview on Tuesday at 10am?",
    "status": "QUEUED",
    "vendor_name": "Acme Messaging",
    "is_sent_by_vendor": False,  # Apploi delivers it
    "sender_email": "[email protected]",
})
print(sent["message"])  # "Message sent successfully"
const sent = await postMessage(98765432, {
  external_id: "msg7f3a91",
  message_type: "email",
  message_direction: "to_candidate",
  subject: "Interview invitation", // required for email
  message_body: "Thanks for applying. Could you come in for an interview on Tuesday at 10am?",
  status: "QUEUED",
  vendor_name: "Acme Messaging",
  is_sent_by_vendor: false, // Apploi delivers it
  sender_email: "[email protected]",
});
console.log(sent.message); // "Message sent successfully"
sent = post_message(98765432, {
  external_id: "msg7f3a91",
  message_type: "email",
  message_direction: "to_candidate",
  subject: "Interview invitation", # required for email
  message_body: "Thanks for applying. Could you come in for an interview on Tuesday at 10am?",
  status: "QUEUED",
  vendor_name: "Acme Messaging",
  is_sent_by_vendor: false, # Apploi delivers it
  sender_email: "[email protected]"
})
puts sent["message"] # "Message sent successfully"

To have Apploi send a text instead, set message_type to text and leave out subject. The applicant needs a mobile number in Apploi. Without one, the request returns 400 (see Retrying safely). Once the text is sent, its external_id in the history is replaced by the delivery provider's id.

Recording a reply from the applicant

Record a reply the applicant sent you with "message_direction": "from_candidate". Leave out is_sent_by_vendor or set it to true: Apploi doesn't deliver replies, and false is refused.

sender_email is still required. Use the recruiter the applicant was replying to.

# 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/applicants/98765432/message \
  -H "X-Api-Key: $APPLOI_API_KEY" \
  -H "Content-Type: application/json" \
  -w "\nHTTP status: %{http_code}\n" \
  -d '{
    "external_id": "msg7f3a9c",
    "message_type": "text",
    "message_direction": "from_candidate",
    "message_body": "Yes, Tuesday at 10am works for me.",
    "status": "received",
    "vendor_name": "Acme Messaging",
    "sender_email": "[email protected]"
  }'
reply = post_message(98765432, {
    "external_id": "msg7f3a9c",
    "message_type": "text",
    "message_direction": "from_candidate",
    "message_body": "Yes, Tuesday at 10am works for me.",
    "status": "received",
    "vendor_name": "Acme Messaging",
    "sender_email": "[email protected]",  # the recruiter the applicant replied to
})
const reply = await postMessage(98765432, {
  external_id: "msg7f3a9c",
  message_type: "text",
  message_direction: "from_candidate",
  message_body: "Yes, Tuesday at 10am works for me.",
  status: "received",
  vendor_name: "Acme Messaging",
  sender_email: "[email protected]", // the recruiter the applicant replied to
});
reply = post_message(98765432, {
  external_id: "msg7f3a9c",
  message_type: "text",
  message_direction: "from_candidate",
  message_body: "Yes, Tuesday at 10am works for me.",
  status: "received",
  vendor_name: "Acme Messaging",
  sender_email: "[email protected]" # the recruiter the applicant replied to
})

What the response means

Some failures on this endpoint arrive with a 200 status. They include the 200 rows below, and also server errors other than a plain 500, such as a 502 or 503 while Apploi is deploying, whose body may not be JSON at all. Treat a request as successful only when the body's data.status is success.

A successful response looks like this:

{
  "data": {
    "status": "success",
    "message": "Message updated successfully"
  }
}

message is Message updated successfully when Apploi only recorded the message and Message sent successfully when Apploi is delivering it.

StatusBodyCauseWhat to do
200data.status is successThe message was saved.Nothing.
200data.status is failed, with "message": "This operation is not allowed, because is_sent_by_vendor is false"is_sent_by_vendor was false on a from_candidate message. Nothing was saved.Send it again without is_sent_by_vendor.
200{"developer_message": "422 Unprocessable Entity: Subject is required for email messages", "extra": null}An email was sent without a subject.Add a subject.
200{"developer_message": "422 Unprocessable Entity: Feature is not enabled for this team", "extra": null}Messaging isn't turned on for the team that owns the job.Ask your Apploi contact to enable it.
200{"developer_message": "422 Unprocessable Entity: Invalid permission for API!!!", "extra": null}The job application belongs to a team your API key can't reach.Check the id.
200Any other body without data, or a body that isn't JSONA temporary error on Apploi's side.See Retrying safely.
400{"message": "Invalid request body"}A required field is missing, a value isn't one of the allowed options, or is_sent_by_vendor isn't true or false.Check the body against the schema on this page.
400{"developer_message": "400 Bad Request: sender_email is required.", "extra": null}sender_email is missing.Add it.
400{"developer_message": "400 Bad Request: No user found with the provided sender_email.", "extra": null}sender_email doesn't belong to an active user on the team that owns the job.Use the email address of a user on that team.
400{"developer_message": "400 Bad Request: Missing phone number for SMS message", "extra": null}Apploi was asked to send a text, but the applicant has no mobile number.Send an email instead, or deliver the text yourself. See Retrying safely.
404{"developer_message": "404 Not Found: Application not found or candidate not found", "extra": null}No job application has that id.Check that you sent the job application's id, not its applicant_id.
403{"message": "Forbidden"}The X-Api-Key header is missing or invalid.See Errors.
5xxVariesSomething failed on Apploi's side.See Retrying safely.

Field limits

A value longer than its limit can cause a 500 error.

  • external_id: up to 36 characters. It comes back on GET /applicants/{applicant_id}/message. If it contains a hyphen, only the part after the first hyphen comes back, so an id without hyphens is easiest to match.
  • status: up to 16 characters.
  • subject: up to 255 characters.
  • Send only the fields described in the body schema on this page. Other fields can cause a 500 error.

Retrying safely

This endpoint doesn't detect duplicates, so sending the same message twice records it twice.

When Apploi delivers a message ("is_sent_by_vendor": false), the message is saved before delivery is attempted. If delivery then fails, the request returns an error (a 400 for a missing phone number, or a 5xx), but the message is still in the history with status set to failed.

Before retrying after a 5xx, after a 200 with no data that isn't one of the errors in the table above, or after a 400 on a message Apploi was delivering, read the history with GET /applicants/{applicant_id}/message and check whether the message is already there:

  • Messages you deliver, emails Apploi sends, and texts Apploi failed to send keep your external_id. Look for it. If the match has status set to failed, Apploi didn't deliver it, so treat it as not sent. After a 400 for a missing phone number, sending the same text again fails the same way: deliver it yourself, or send an email instead.
  • Texts Apploi sent successfully don't: their external_id is replaced by the delivery provider's id. Look instead for a to_candidate text with the same message_body and a sent_at within the last few minutes. A 5xx doesn't prove the text wasn't sent. A request that takes longer than 29 seconds returns 504 even if the text goes out afterwards.

Send the message again only if it isn't there, or if you intend a second attempt.

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 message details. All fields are required except 'subject' when message_type is 'text'.

Required Fields:

  • external_id: Vendor-specific message identifier
  • message_type: 'text' or 'email'
  • message_body: Message content
  • status: Message status (e.g., 'SENT', 'DELIVERED')
  • vendor_name: Vendor/service name (e.g., 'abc')
  • message_direction: 'to_candidate' or 'from_candidate'
  • sender_email: Required for partner integrations. Email address of an active Apploi user on the team that owns the job. Requests without it, or with an email address that doesn't match such a user, return 400.

Optional Fields:

  • subject: Required when message_type is 'email'
  • is_sent_by_vendor: Boolean flag (true = vendor sends, false = Apploi sends) default value is true if not passed in payload

Example - Text Message to Candidate (Vendor Sends):

{
  "external_id": "msg7f3a90",
  "message_type": "text",
  "message_body": "Thank you for applying! We'd like to schedule an interview.",
  "status": "SENT",
  "vendor_name": "abc",
  "message_direction": "to_candidate",
  "is_sent_by_vendor": true,
  "sender_email": "[email protected]"
}

Example - Email to Candidate (Apploi Sends):

{
  "external_id": "msg7f3a91",
  "message_type": "email",
  "message_body": "We are excited to move forward with your application...",
  "subject": "Interview Invitation",
  "status": "SENT",
  "vendor_name": "abc",
  "message_direction": "to_candidate",
  "is_sent_by_vendor": false,
  "sender_email": "[email protected]"
}

Example - Response from Candidate:

{
  "external_id": "msg7f3a9c",
  "message_type": "text",
  "message_body": "I am available for an interview next week.",
  "status": "DELIVERED",
  "vendor_name": "abc",
  "message_direction": "from_candidate",
  "sender_email": "[email protected]"
}

Request body for creating a new message for an application. Messages can be sent to or from candidates.

string
required
length ≥ 1

External identifier for the message from the vendor system.

boolean

[if not sent in payload default: true] Controls message delivery. Set to true if vendor handles SMS/email delivery. Set to false if Apploi should send the message via its infrastructure.

string
required
length ≥ 1

The content/body of the message to be sent to or from the candidate.

string
enum
required

Direction of the message flow. Use 'to_candidate' for messages sent to the candidate (outbound), 'from_candidate' for candidate responses (inbound).

Allowed:
string
enum
required

Type of message being sent. Must be 'text' for SMS or 'email' for email messages.

Allowed:
string

Required for partner integrations. Email address of an active Apploi user on the team that owns the job. Requests without it, or with an email address that doesn't match such a user, return 400.

string
required
length ≥ 1

Current status of the message in the vendor system (e.g., SENT, DELIVERED, FAILED).

string

Email subject line. Required when message_type is 'email', optional for text messages.

string
required
length ≥ 1

Name of the vendor/service sending the message.

Responses

400

400 response

403

403 response

404

404 response

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