Disposition Data

Table of contents

About the Disposition Data Integration

What is disposition data?
Disposition data is any kind of update that happens to an application in your ATS. This can include changes in a recruiter's workflow or actions a recruiter/candidate takes AFTER they apply. Examples of disposition data:
  • A new candidate entered the ATS
  • A candidate was contacted
  • A candidate was interviewed
  • A candidate was given an offer
  • A candidate was rejected
  • A candidate was hired
Why do I want to send disposition data to Indeed?
  1. We can make better decisions about (1) how many and (2) what kind of applications to send you in the future when we get feedback about what happens to the applications we previously sent you.
  2. Help job seekers. "Close the loop" on what happens to applies that start on Indeed. The disposition data integration helps job seekers indirectly by giving Indeed a better idea of which jobs are still active and which employers are responsive.
How do I upload disposition data to Indeed?
  1. Have Indeed Apply set up so that your ATS can receive applications from Indeed.
  2. Request a disposition data integration by contacting the Indeed Alliances team at [email protected]. Make sure to read the Integration Process Overview, API Requirements, and Instructions sections on this page to see if you can comply. If you can't make sure to message potential problem areas in your email.

Integration Process Overview

  1. Contact your Indeed Alliances manager (or send an email to [email protected]) to request an API key
  2. You will receive an email with your API keys from Indeed
  3. Store these keys in a safe place and use only for the Disposition Data Integration. Do not use them for any other Indeed integration/API.
  4. Read all of our documentation below
  5. Send an upload using your test API key. Your test upload should contain real data, including real Indeed Apply IDs.
  6. Contact your Indeed Alliances manager and let them know the test file is ready for review
  7. Indeed will manually review the file format and contact you within 1 week (usually sooner)
  8. Your Indeed Alliances manager will contact you with your review results. If your test file passes, you may start sending uploads to production by using your production API key.
  9. After you start sending production files, Indeed encourages, but does not require further test uploads.

Requirements

Full code examples for using the API are below.

Typical Integration Setup

Partners should set up a scheduled job that runs periodically to generate a report of applications with status changes. An example would be a csv with headers: disposition_timestamp, apply_id and status.

Full example: An application may be updated multiple times per day and you can upload it every 24 hours. However, the same row should not appear twice or be resubmitted.

Note that the csv file should only contain applications that have had a change in status. Here are examples of correct and incorrect csv file uploads for a given series of application status changes.

Sample Correct CSV

Sample Correct XML (for legacy integrations only)

Correct

Day 1 - File 1
disposition_timestamp apply_id status
2019-01-01T01:00:00z appid001 NEW

Day 2 - File 2
disposition_timestamp apply_id status
2019-01-02T01:00:00z appid002 NEW


Day 3 - File 3
disposition_timestamp apply_id status
2019-01-03T01:00:00z appid001 CONTACTED
2019-01-03T01:00:00z appid003 NEW


This example is correct because data is included only for
  • new applications
  • when the status of an apply_id has changed
Incorrect

Day 1 - File 1
disposition_timestamp apply_id status
2019-01-01T01:00:00z appid001 NEW

Day 2 - File 2
disposition_timestamp apply_id status
2019-01-01T01:00:00z appid001 NEW
2019-01-02T01:00:00z appid002 NEW

Day 3 - File 3
disposition_timestamp apply_id status
2019-01-02T01:00:00z appid002 NEW
2019-01-03T01:00:00z appid001 CONTACTED
2019-01-03T01:00:00z appid003 NEW

This example is incorrect for 2 reasons:

  • (2019-01-01T01:00:00z, appid001, NEW) is uploaded twice where it should have only appeared on day 1
  • (2019-01-02T01:00:00z, appid002, NEW) is uploaded twice where it should have only appeared on day 2

You should only submit data ONLY where an apply's status has changed. In other words, each unique row (disposition_timestamp, apply_id, status) should be uploaded only once.

Field Definitions

Field Required? Description
apply_id Yes

The unique identifier for the job application that is used to reference the employer, candidate, job, and more.

This is the id field in the Indeed Apply JSON application data. For more information this references the id field here: http://techdocs.indeedeng.io/json-application-data/.

status Yes

IMPORTANT: Read this carefully.

Normalized application status.

Indeed uses the Status field to understand what happens to applications once they are in your ATS. There are 6 different statuses we look for (see below).

You must map all of your ATS's statuses into these 6 Indeed Statuses.

It is absolutely crucial you accurately map the statuses from your ATS into the 6 categories. Without an accurate mapping, Indeed cannot reliably determine what happens to an application. If you support custom statuses in your ATS, please mention this to your Indeed Alliances manager.

Indeed Status Description
NEW The application arrived via Indeed Apply
CONTACTED

The candidate was contacted by phone, email, etc.

This status should be sent even if the application status on your ATS has not changed. Indeed uses this as a signal to help determine employer responsiveness, which can affect the quality and volume of candidates an employer receives.

INTERVIEWED The candidate has been interviewed.
OFFERED An offer of employment has been sent to a candidate.
HIRED The candidate accepted an offer of employment.
REJECTED A candidate has been explicitly rejected or all remaining candidates rejected after the role closed.

If you are unable to comply with this step, please contact your Indeed Alliances manager.

disposition_timestamp Yes ISO 8601 format timestamp with timezone information for when the status change occurred.
E.x. November 5, 1994 at 8:15 am Eastern Time would be: 1994-11-05T08:15:30-05:00
  • The separator between date and time must be a capital 'T'.
  • Timezone information is required.
  • Use the format +/-HH:MM for timezone information, unless it is UTC (see next point).
  • For UTC, "+00:00", "Z", and "z" all valid, but "-00:00" is not valid.
  • Please use `00:00:00` for hour/minute/second if you do not have actual time information.

API Instructions - Exporting Data to Indeed

Overview/Integration

Our API allows your organization to programmatically deposit disposition information. The integration takes two steps:

1. Obtain a presigned Amazon S3 URL by POST request to this API using the assigned API Key.

2. Make a PUT request to upload the file to the URL returned in Step 1.

Note: To prevent uploading files with the same name, use a naming convention that allows you to distinguish your files from other files in your organization.

Obtaining a signed S3 URL

To receive the signed S3 bucket URL, create a POST request to our API with the file names you are uploading. A presigned upload URL will be returned for each file. Requirements and responses are described below.

Required Request Headers

Parameters

Limitations

Success Response

A HTTP status of 200 and a JSON object/dictionary will be returned with keys as the file_names sent and values corresponding to the presigned S3 URL. See examples below for details.

Error Response

A HTTP status non 200 and dictionary containing the keyError will be returned and give more information.

Uploading Files

Once you have the presigned URL, you can upload files to the AWS Bucket using a PUTrequest. An example of doing so is shown below, however you may also refer to the official AWS documentation.


Code Examples

Use a unix-based system with bash and the curl command.

#  Get the presigned URLs
curl \
-H "Content-Type: application/json" \
-H "token: XXXXXXXXX" \
-H "Accept: application/json" \
--request POST \
--data '{"file_names":["test_file_1.csv","test_file_2.csv"]}' \
https://indeed-atsdi.com/api/get_upload_url

##### API Response #####
# {
#    "test_file_1.csv": "https://blah.s3.amazonaws.com/presigned_url_1",
#    "test_file_2.csv": "https://blah.s3.amazonaws.com/presigned_url_2"
# }
##### END Response #####

#  Upload files to S3, one at a time.

#  Put pre-signed URL in quotes.
curl -X PUT -T /path/to/test_file_1.csv \
-L "https://blah.s3.amazonaws.com/presigned_url_1"


#  Put pre-signed URL in quotes.
curl -X PUT -T /path/to/test_file_2.csv \
-L "https://blah.s3.amazonaws.com/presigned_url_2"

Use python 3 and the requests library

import requests
import json

url = 'https://indeed-atsdi.com/api/get_upload_url'
file_paths = ['/path/to/test_file_1.csv', '/path/to/test_file_2.csv']
API_KEY = 'XXXXXXX'

# File tuple [0] = path, [1] = basename.
file_infos = [(fp, ntpath.basename(fp)) for fp in file_paths]


headers = {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
    'token': API_KEY
}

#  This gets the presigned URLs
r = requests.post(url,
                  headers=headers,
                  data=json.dumps({'file_names': [fi[1] for fi in file_infos]}))
try:
    r.raise_for_status()
except:
    print("Error occurred.")
    print (r.json())

#  Upload file(s) to AWS.
s3_signed_urls = r.json()
for file_info in file_infos:
      with open(file_info[0], 'rb') as data:
          upload_result = requests.put(s3_signed_urls[file_info[1]], data=data)
          try:
              upload_result.raise_for_status()
          except:
              print("Issue uploading file to AWS.")

Use ruby and only standard packages

require 'net/http'
require 'net/https'
require 'uri'
require 'json'

Tuple = Struct.new(:_1, :_2)

api_url = URI.parse('https://indeed-atsdi.com/api/get_upload_url')
file_paths = ['/path/to/test_file_1.csv', '/path/to/test_file_2.csv']
api_key = 'XXXXXXX'

names = []
file_info = []
file_paths.each do |path|
  file_info.push(Tuple.new(path, File.basename(path)))
  names.push(File.basename(path))
end

header = {'Accept': 'application/json',
          'Content-Type': 'application/json',
          'token': api_key}
data = {file_names: names}

#  Access indeed api
api_response = nil
Net::HTTP.start(api_url.host, :use_ssl => true) do |http|
  api_response = http.send_request("POST", api_url.request_uri, data.to_json, header)
end

case api_response
when Net::HTTPSuccess
  json = JSON.parse(api_response.body)

  # Upload to S3.
  file_info.each do |upload_file|
    s3_url = URI.parse(json[upload_file._2])
    file = File.open(upload_file._1, "rb")
    file_data = file.read
    file.close
    s3_response = nil
    Net::HTTP.start(s3_url.host, :use_ssl => true) do |http|
        s3_response = http.send_request('PUT', s3_url.request_uri, file_data, {
        # Content type has to be here, even if set to '', or else 403 error.
         "content-type" => '',
        })
      end
    case s3_response
    when Net::HTTPSuccess
      print 'Successfully uploaded ' + upload_file._1 + "\n"
    else
      print 'Error uploading file ' + upload_file._1 + "\n"
      print s3_response.inspect + "\n"
    end
  end
else
  print 'Bad Response from Indeed API.' + "\n"
  print api_response.inspect + "\n"
end

Use PHP with standard libraries and curl PHP module.

<?php
class IndeeddispositionClient{
	private static $api_key = 'XXXXXXXXX';
	private static $api_url = 'https://indeed-atsdi.com/api/get_upload_url';

  static function get_s3_url($filename) {
    $url = self::$api_url;
    $data = '{"file_names":["'.$filename.'"]}';
    $headers = array(
      'Content-Type: application/json',
      'token: ' . self::$api_key,
      'Accept: application/json'
    );
    $options = array(
      CURLOPT_POST => 1,
      CURLOPT_POSTFIELDS => $data,
      CURLOPT_HTTPHEADER => $headers,
      CURLOPT_RETURNTRANSFER => 1
    );

    $curl = curl_init($url);
    curl_setopt_array($curl, $options);
    $res = curl_exec($curl);
    curl_close($curl);

    return $res;
  }

  static function upload_to_s3($file_path_string, $signed_url){
      $fh = fopen($file_path_string, 'rb');
      $options = array(
        CURLOPT_VERBOSE => 1,
        CURLOPT_POST => 1,
        CURLOPT_RETURNTRANSFER => 1,
        CURLOPT_URL => $signed_url,
        CURLOPT_INFILE => $fh,
        CURLOPT_INFILESIZE => filesize($file_path_string),
        CURLOPT_PUT => 1
      );
      $ch = curl_init();
      curl_setopt_array($ch, $options);

      $result = curl_exec($ch);
      if (curl_errno($ch)) {
          echo 'Error:' . curl_error($ch);
      }
      fclose($fh);
      curl_close($ch);
      return $result;
    }
}


$file_paths = array("test_file_1.csv","test_file_2.csv");

foreach ($file_paths as $file_path){
  if (!file_exists($file_path)) {
    print 'Cannot find file '.$file_path."\n";
    throw new Exception('File not found '.$file_path);
  }
  $file_name = basename($file_path);
  print "Uploading ".$file_name ."\n";
  print "... ".$file_name ."\n";
  $s3_url = json_decode(IndeeddispositionClient::get_s3_url($file_name), true)[$file_name];
  $result = IndeeddispositionClient::upload_to_s3($file_path, $s3_url);

  print $result;

  print "\nFinished with ".$file_name ."\n";
}

?>



Frequently Asked Questions

  1. Should I send an empty file for periods that have no data?
    No. Please do not submit empty files.
  2. Can I send data in smaller intervals than one hour?
    No. Please aggregate your data before sending it. That said, we are currently working on a solution to support more frequent upload intervals. If this functionality interests you, please let your Indeed Alliances manager know when you get in touch with them.
  3. My ATS supports custom statuses, how can I map them appropriately?
    Please reach out to an Indeed Alliances manager for assistance.
  4. SignatureDoesNotMatch error when trying to upload a file to S3 using a pre-signed upload URL.
    This is a problem with S3, with a tragically opaque error name. Double-check the headers you are sending. Do NOT send a "Content-Type" header to S3. Refer to S3 documentation if you continue to have this issue.

Request Help

To receive assistance, please contact our integration support team at: [email protected]

Disclaimer

While our integration documentation is publicly available as a reference to understand and plan your integration with Indeed, it is intended to be implemented only by ATS partners who have signed a Master Services Agreement with us.