# Quick Start

# Install cURL

This quick start guide uses cURL to send HTTP GET and POST requests to Cornerstone. cURL is a command line tool that comes pre-installed on most Linux and Mac OS systems. To install cURL in your Windows systems, please see this article: Using cURL.

While the quick start guide uses cURL, you can use any third party tool to construct your API requests. Note that cURL is an open source tool. Cornerstone does not provide any support for installing or troubleshooting cURL.

# Register an OAuth 2.0 Application

Cornerstone's APIs use the OAuth 2.0 authorization framework. Currently, Cornerstone only supports OAuth 2.0's Client Credential grant type which is ideal for system-to-system integration. If you are not familiar with OAuth 2.0, please click here for a brief overview of this industry standard authentication and authorization framework.

Every application that you intend to integrate using Cornerstone's APIs must be registered in the API Management page. You can register multiple applications with Cornerstone to get a distinct client ID and secret for each application. If you are not familiar with the steps to register an application, please follow the steps documented here.

Audit History

Each OAuth 2.0 application must be associated with a user account that functions as a "service account". Any records you load or update using the Bulk API will have an audit history indicating that the record was created/updated by the user associated with the OAuth 2.0 application. For example, if your OAuth 2.0 application is associated with a user account called 'API ServiceAccount' and you create an employee record using the Bulk API, the User Record Modification History page in the Cornerstone UI will show that the employee record was created by the 'API ServiceAccount'. Keep this mind while choosing a name for your service account.

The user account tied to your OAuth 2.0 application must have the following permissions. Note that these permissions only apply for employee loads. For a complete list of permissions needed for each load type, see General Guidelines - Security Permissions.

Permission Name Category Description
Access Edge Bulk API Edge Provides access to Edge Bulk API
Access Bulk API - Employee Edge Provides access to Edge Bulk API - Employee load

Additionally, while registering your application, be sure to select the Bulk API scopes.

Assuming you have setup cURL and registered your OAuth 2.0 application, you can start sending your API requests to Cornerstone. Cornerstone's API endpoints have a specific pattern:

Stage
https://{corpname}-stg.csod.com/services/api/{resource_address}

Pilot
https://{corpname}-pilot.csod.com/services/api/{resource_address}

Production
https://{corpname}.csod.com/services/api/{resource_address}

Here, corpname is your portal name. resource_address is the location where Cornerstone has defined the specific API endpoint. The rest of this quick start guide uses the production URL.

# Step 1: Get Access Token

You will need to first get an OAuth 2.0 access token since each subsequent call to any Bulk API endpoint requires you to pass the bearer token in the authorization header. To acquire an access token, send a POST request to /services/api/oauth2/token. In the request body, include the clientId, clientSecret, grantType, and scope in JSON format. For grantType, the value should be client_credentials. The value for scope will vary depending on the operation you are trying to perform. See List of Security Permissions and Scopes for the scopes specific to the Bulk API.

Here's a cURL command you can execute.

  • Replace corpname with your portal name
  • Modify the values for clientId and clientSecret with your registered OAuth 2.0 application's credentials
# Access Token Request
curl -X POST \
  https://{corpname}.csod.com/services/api/oauth2/token \
  - H 'Content-Type: application/json' \
  -d '{
    "clientId": "dbq2kjiql2c4",
    "clientSecret": "l4nqwza+7RbK0rrzs16VMeH+5dWEsFjsRSXtQ0MwL+TSSWvZGliUkgUfIenAk0+1Yx0yPtTs+bSmlotR2KCVGA==",
    "grantType": "client_credentials",
    "scope": "bulkapijob:read bulkapiimport:read bulkapischema:read bulkapijob:create bulkapiimport:load"
  }'

If your credentials are valid, Cornerstone will return the access_token along with the token_type (always bearer), token validity duration in seconds, and scope. Here's an example of a successful response.

# Access Token Successful Response
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: private
{
  "access_token": "ZWRnZQ:sh97VjZWQJ9pvYVqKiapZYDeLjM8pvFrnku8i6ckuTx8QXaSnIkI4rV7MF21LD2A2A5vKXQqSydq7kGIGV2bQA==",
  "expires_in": 3600,
  "scope": "bulkapijob:read bulkapiimport:read bulkapischema:read bulkapijob:create bulkapiimport:load",
  "token_type": "Bearer"
}

If you receive an HTTP 400 error, verify that your clientId and clientSecret are correct and retry your request.

Note that access tokens have a validity period. If the validity period passes, your subsequent API calls will fail with an HTTP 401 error and you will need to make the above call again to get a new access token.

# Step 2: Get Schema

Before you can create your data template, you must first retrieve the schema specific to your load type and portal. To do this, send a POST request to /services/api/x/bulk-api/v1/schemas. Include the bearer token received in step 1 as an authorization header. In the request body, include the type and associated settings. Note that the request body will vary slightly for each load type.

A POST to "get" schema

Even though you are "getting" a schema from Cornerstone, the request to Cornerstone must be a POST operation because you need to include a request body.

Here's a cURL command you can execute.

  • Replace corpname with your portal name.
  • Replace the bearer token in the Authorization header with the token you acquired in step 1.
  • Within the settings object, you can adjust the LoadPrimaryKey and default_culture.
    • LoadPrimaryKey is an employee load specific field. It lets you define the primary identifier of your data. Valid options are 0 and 1. 0 indicates that the 'User ID' is the primary identifier. 1 indicates that the GUID is the primary identifier. Cornerstone will use your definition of the primary identifier to adjust the list of required fields included in the response.
    • default_culture lets you define the culture for your data. Cornerstone will use this to adjust the default value for certain fields it includes in the response.
# Get Schema Request
curl -X POST \
  https://{corpname}.csod.com/services/api/x/bulk-api/v1/schemas \
  -H 'Authorization: Bearer ZWRnZQ:49eb8e8bb33e3ab24856b8a1f6bd5e454c57957f03d25c9bf579dd2accb3e6ca' \
  -H 'Content-Type: application/json' \
  -d '{
    "type":"chr.employee",
    "settings": {
      "LoadPrimaryKey":0,
      "default_culture":"en-US"
    }
  }'

Here's an example of a successful response from Cornerstone. The response body is in JSON format and has been truncated here for readability. To see the full schema for your portal, make the actual API call to the /schemas endpoint.

  • The schema includes a complete list of valid values for enumerated fields. These include standard as well as custom fields.
  • The schema includes a list of required fields under the required object.
  • The schema includes a list of conditionally required fields under the dependencies object.
  • Fields with names such as CF_DateField#11 represent custom fields a client administrator has created in their Cornerstone portal. The field name tells you the type of custom field (date, text box, dropdown etc.). The title of the custom field is included within the title field.
  • Specifically for the Employee load,
    • If you see fields with names such as DynamicOUs_52971, those are custom OUs a client administrator has created in their Cornerstone portal. The title of the custom OU is included within the title field.
    • If you see fields with names such as DynamicUserRelations_32768, those are custom user relationships a client administrator has created in their Cornerstone portal. The title of the custom relationship is included within the title field.
    • When using GUID's to update an employee record, all mapped fields are considered as optional. Only exception is if you are updating conditionally required fields (eg: User Type -> Sub-type), the dependent field becomes required.
# Get Schema Response
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: private
{
  "schema": {
  "properties": {
    "EmployeeIdentificationData_UserId": {
      "minLength": 1,
      "maxLength": 100,
      "trim": "leftRight",
      "caseSensitive": false,
      "title": "User ID",
      "description": "Unique identifier for the user",
      "type": "string"
    },
    "PersonData_NameData_FirstName": {
      "minLength": 1,
      "maxLength": 200,
      "trim": "leftRight",
      "title": "First Name",
      "description": "First Name",
      "type": "string"
    },
    "PersonData_NameData_LastName": {
      "minLength": 1,
      "maxLength": 200,
      "trim": "leftRight",
      "title": "Last Name",
      "description": "Last Name",
      "type": "string"
    },
    "Gender": {
      "enum": [
        "female",
        "male",
        "not known",
        "not specified"
      ],
      "trim": "leftRight",
      "caseSensitive": false,
      "title": "Gender",
      "description": "User's gender",
      "type": "string"
    },
    "Division": {
      "minLength": 1,
      "maxLength": 100,
      "trim": "leftRight",
      "title": "Division",
      "type": "string"
    },
    "DynamicOUs_524291": {
      "minLength": 1,
      "maxLength": 100,
      "trim": "leftRight",
      "title": "Employee Type",
      "type": "string"
    },
    "DynamicUserRelations_32768": {
      "maxLength": 100,
      "trim": "leftRight",
      "caseSensitive": false,
      "title": "Compensation Specialist",
      "type": "string"
    },
    "CF_ShortTextBox#23": {
      "maxLength": 100,
      "title": "Retirement Account Type",
      "type": "string",
      "default": ""
    },
    "CF_MultiCheckbox#46": {
      "title": "Product Interest",
      "anyOf": [
        {
          "\$ref": "#/definitions/CF_MultiCheckbox#46"
        },
        {
          "enum": [ "" ],
          "type": "string"
        }
      ],
      "default": ""
    }
  },
  "required": [
    "EmployeeIdentificationData_UserId",
    "PersonData_NameData_FirstName",
    "PersonData_NameData_LastName",
    "Division"
  ],
  "dependencies": {
    "\_ActivationDt": [ "_DeactivationDt" ],
    "\_DeactivationDt": [ "_ActivationDt" ]
  },
  "definitions": {
    "CF_MultiCheckbox#46": {
      "id": "CF_MultiCheckbox#46",
      "anyOf": [
        {
          "minLength": 1,
          "trim": "leftRight",
          "type": "string"
        },
        {
          "uniqueItems": true,
          "items": [
            {
              "anyOf": [
                {
                  "enum": [ 33, 34, 35 ],
                  "type": "integer"
                },
                {
                  "enum": [ "Product A", "Product B", "Product C" ],
                  "trim": "leftRight",
                  "caseSensitive": false,
                  "type": "string"
                }
              ]
            }
          ],
          "type": "array"
        }
      ]
    }
  },
  "identity": [ "EmployeeIdentificationData_UserId" ],
  "id": "csod:chr.employee",
  "type": "object"
  }
}

See also

# Step 3: Prepare your Data Template

Using the schema returned in step 2, create your data template. The Bulk API accepts data in comma separated values (CSV) and JSON format.

For CSV, the Bulk API requires you to follow the RFC 4180 standards. More specifically, ensure the following:

  • Each record is located on a separate line, delimited by a line break (CRLF)
  • Include a header line with the fields names defined in the schema. The header should include the property names and not the field title. For example, use EmployeeIdentificationData_UserId and not 'User ID'.
  • Within the header and in each record, values must be separated by commas.
  • Field values containing line breaks (CRLF), double quotes, and commas should be enclosed in double-quotes.
  • If double-quotes are used to enclose fields, then a double-quote appearing inside a field must be escaped by preceding it with another double quote.

Here's an example of employee data in CSV format.

EmployeeIdentificationData_UserId,PersonData_NameData_FirstName,PersonData_NameData_LastName,EmployeeIdentificationData_Username,DirectManager,PersonData_ContactData_EmailAddressData_Primary_EmailAddress,WorkerStatusData_LastHireDate,Division,Position,Location,DynamicOUs_524291,DynamicUserRelations_16384,CF_MultiCheckbox#47
johnsmith01,John,Smith,johnsmith01,904321062,johnsmith01@acme.com,2019-12-01,SUPERVISORY_ORGANIZATION-3-9244,Account Manager,Los Angeles,Full Time,918634631,Manager;Administrator
janedoe02,Jane,Doe,janedoe02,904321062,janedoe02@acme.com,2019-10-01,SUPERVISORY_ORGANIZATION-3-9244,Accountant, San Jose,Intern,918634631,
rajkumar01,Raj,Kumar,rajkumar01,903300421,rajkumar01@acme.com,2019-12-01,SUPERVISORY_ORGANIZATION-3-9244,CS-Customer Service#37486,OR,Part Time,918634631,Administrator
jamesanderson,James,anderson,jamesanderson,937393194,jamesanderson@acme.com,2019-11-25,SUPERVISORY_ORGANIZATION-3-9244,Computer Support Specialist,Brisbane,Full Time,918634631,IT
904321062,Aime,Jones,aimejones,948752281,aimejones@acme.com,2018-01-29,SUP_000012151,Corporate Services Manager,Shanghai,Full Time,987798693,Manager;Administrator

If you wish to send your data in JSON format, use the general key-value pair notation. Here's an example of employee data in JSON format.

[
    {
        "EmployeeIdentificationData_UserId": "johnsmith01",
        "PersonData_NameData_FirstName": "John",
        "PersonData_NameData_LastName": "Smith",
        "EmployeeIdentificationData_Username": "johnsmith01",
        "DirectManager": "904321062",
        "PersonData_ContactData_EmailAddressData_Primary_EmailAddress": "johnsmith01@acme.com",
        "WorkerStatusData_LastHireDate": "2019-12-01",
        "Division": "SUPERVISORY_ORGANIZATION-3-9286",
        "Position": "Account Manager",
        "Location": "Los Angeles",
        "DynamicOUs_524291": "Full Time",
        "DynamicUserRelations_16384": "918634631",
        "CF_MultiCheckbox": "Manager;Administrator"
    },
    {
        "EmployeeIdentificationData_UserId": "janedoe02",
        "PersonData_NameData_FirstName": "Jane",
        "PersonData_NameData_LastName": "Doe",
        "EmployeeIdentificationData_Username": "janedoe02",
        "DirectManager": "904321062",
        "PersonData_ContactData_EmailAddressData_Primary_EmailAddress": "janedoe02@acme.com",
        "WorkerStatusData_LastHireDate": "2019-10-01",
        "Division": "SUPERVISORY_ORGANIZATION-3-9286",
        "Position": "Accountant",
        "Location": "San Jose",
        "DynamicOUs_524291": "Full Time",
        "DynamicUserRelations_16384": "918634631",
        "CF_MultiCheckbox": "Administrator"
    }
]

Irrespective of the format, note that:

  • For OU fields you must include the OU ID and not the OU title. This applies to both standard and custom OUs.
  • For employee relationship fields you must include the related user's user ID and not their username, email, or any other user identifier. This applies to both standard and custom relationships.
  • For custom fields of type drop down, radio button, checkbox, and multi-checkbox, you must use the enumerated values listed in the schema.

# Step 4: Create a Job

A job is a container for your bulk load operation. Each bulk load operation must start with the creation of a job. To create a job, send a POST request to the /services/api/x/bulk-api/v1/jobs endpoint.

Include the bearer token received in step 1 above as an authorization header. In the request body, include details for each import you wish to perform using this job. Each job can contain multiple imports (up to a limit of 20). For example, you can include an employee and several OU loads in a single job. For each import include the type, an optional label and associated settings. Note that the request body will vary slightly for each load type.

Here's a cURL command you can execute to create a job to load employees.

  • Replace corpname with your portal name.
  • Replace the bearer token in the Authorization header with the token you acquired in step 1.
  • The label fields at both the job and import levels are optional, but we highly recommend including them. A client administrator can see the labels in Cornerstone within the Bulk API History page. Use the labels as a way for client administrator to identify loads that occurred through your integration.
  • In the optional callback field you can include a URL you have set up on your end where Cornerstone can send a notification once the job completes processing.
    • The URL must be an HTTPS endpoint that is secured by an SSL certificate. Including an HTTP endpoint in this field will result in a validation error.
    • When all the imports in a job complete, Cornerstone will send a POST request to the callback URL. See Step 6: Check Job Status for more information.
    • Cornerstone does not support any authentication on the callback URL. However, we recommend that you embed a single-use token within the URL for each job request. This way you will know that the POST request is coming from Cornerstone.
  • For more guidance on setting up your callback endpoint, please read General Guidelines - Callback.
  • Within the settings object, you can adjust the following:
    • LoadPrimaryKey lets you define the primary identifier of your data. Valid options are 0 and 1. 0 indicates that the 'User ID' is the primary identifier. 1 indicates that the GUID is the primary identifier.
    • ReconcileUsers allows you to mark employee records as inactive if they aren't included in your payload. If set to true, the Bulk API will mark any employee records in Cornerstone that aren't in your payload as inactive. If set to false, the Bulk API will not touch any employee records that aren't in your payload.
    • AllowInactiveOus lets you define whether the Bulk API should allow you to associate employee records in your payload with inactive OUs in Cornerstone. If set to true, you can associate employee records with inactive OUs. If set to false, the Bulk API will not load any records from your payload that are associated with inactive OUs in Cornerstone.
    • AllowInactiveUsers lets you define whether the Bulk API should allow you to associate employee records in your payload with inactive users in Cornerstone. This applies to standard and custom user relationships. If set to true, you can associate employee records with inactive users. If set to false, the Bulk API will not load any records from your payload that are associated with inactive users in Cornerstone.
    • use_default_when_missing lets you define what the Bulk API should do if your payload contains no values for certain optional fields. If set to true, the Bulk API will use the default value an administrator has configured for such fields. If set to false, the Bulk API will ignore any fields with no values. Note the following regarding this option:
      • This option only applies to existing employee records that you are updating using the Bulk API. For new employee records, the Bulk API will always use the default value for optional fields that don't have a value or have an invalid value.
      • This option only applies if the optional field is present in your payload but has no value. If an optional field is not present in your payload, the Bulk API will not touch that field.
    • default_culture lets you define the language that your data is in. This is useful for fields that can be localized in Cornerstone.
    • datetime_culture is an optional field that lets you define the format of date fields in your payload. Valid values are en-US (month first), fr-FR (day first), and en-GB (year first).
    • number_culture is an optional field that lets you define the format of numeric fields in your payload. Valid values are en-US (decimals are separated with a period "."), fr-FR (decimals are separated with a comma ","), and es-ES (decimals are separated with a comma "," AND thousands are grouped with a dot ".").
# Create Job Request
curl -X POST \
  https://{corpname}.csod.com/services/api/x/bulk-api/v1/jobs \
  -H 'Authorization: Bearer ZWRnZQ:0c8254c9081b3c2402475a1784e9bb0a966add396ed324fa408b6f6524c0aa64' \
  -H 'Content-Type: application/json' \
  -d '{
    "label":"HRIS Sync",
    "callback":"https://www.acme.com/csod-bulkapi-callback",
    "imports":[
      {
        "type":"chr.employee",
        "label":"user load",
        "settings":{
          "loadprimarykey":0,
          "reconcileusers":false,
          "AllowInactiveOus":true,
          "AllowInactiveUsers":true,
          "use_default_when_missing":true,
          "default_culture":"en-US",
          "datetime_culture":"en-US",
          "number_culture":"en-US"
        }
      }
    ]
  }'

A successful job creation indicates that you have now created a container for your data loads. Creating the job itself does not actually load any data in Cornerstone (because you haven't sent any data yet). The job will start loading data as your start sending the imports for that job (see step 5).

Here's an example of a successful response for the create job request.

The response includes a unique identifier for the job and for each import. You will need these IDs in subsequent API calls. The owner_ref field in the response refers to the user ID of the user associated with your OAuth 2.0 application. The response includes a field called remaining_capacity at both the job and import levels. This field tells you how many more jobs you can create before reaching the maximum limit. It also tells you how many more loads you can perform of a given type. Cornerstone enforces these limits at both the jobs and import levels. Note that the capacity is only consumed when the imports are created. They are released back as the imports complete. Creating jobs without actually creating the associated imports does not consume any capacity. The status_url field provides the URL to retrieve the status of your job by sending a GET request to this URL.

# Create Job Response
HTTP/1.1 201 CREATED
Content-Type: application/json;charset=UTF-8
Cache-Control: private
{
  "job_id": "c8352637-bb1a-4960-ab91-e4794bdd7bea",
  "create_date": "2019-12-04T04:32:58.697+00:00",
  "owner_ref": "bulk-apis-service-account",
  "status": "ready",
  "label": "HRIS Sync",
  "remaining_capacity": 50,
  "imports": [
    {
      "import_id": "4f917dd5-c8ee-4462-83e6-eeaf030da53a",
      "type": "chr.employee",
      "label": "User Load",
      "remaining_capacity": 5
    }
  ],
  "status_url": "/services/api/x/bulk-api/v1/jobs/c8352637-bb1a-4960-ab91-e4794bdd7bea"
}

# Step 5: Create an Import

An import is a data load within the job you just created in step 4. This is where you will include the data from step 3 in the request body.

To create an import (in other words to start a load), send a POST request to the /services/api/x/bulk-api/v1/imports/{import_id} endpoint. Replace the import_id URL parameter with the ID you received in the response to the create job request in step 4.

Here's a cURL command you can execute to create an import to load employees.

  • Replace corpname with your portal name.
  • Replace the import_id url parameter with the value you received in the response to the create job request in step 4.
  • Replace the bearer token in the Authorization header with the token you acquired in step 1.
  • Note the content-type header has a value of text/csv because the content in this example is in CSV format. If you plan to send data in JSON format, change the content-type header to application/json.
  • If your payload is larger than 20 MB, you will need to compress your payload using gzip encoding. If you decide to gzip encode your payload, be sure to add content-encoding = gzip in the header.
  • In the request body, include your data for the load.
# Create Import Request
curl -X POST \
  https://{corpname}.csod.com/services/api/x/bulk-api/v1/imports/{import_id} \
  -H 'Authorization: Bearer ZWRnZQ:920e6642a40d2b859aabbe1c7c9e4dcdf2837a43a7e6eeb145a111329e3e4479' \
  -H 'Content-Type: text/csv' \
  -d 'EmployeeIdentificationData_UserId,PersonData_NameData_FirstName,PersonData_NameData_LastName,EmployeeIdentificationData_Username,DirectManager,PersonData_ContactData_EmailAddressData_Primary_EmailAddress,WorkerStatusData_LastHireDate,Division,Position,Location,DynamicOUs_524291,DynamicUserRelations_16384,CF_MultiCheckbox#47
johnsmith01,John,Smith,johnsmith01,904321062,johnsmith01@acme.com,2019-12-01,SUPERVISORY_ORGANIZATION-3-9244,Account Manager,Los Angeles,Full Time,918634631,Manager;Administrator
janedoe02,Jane,Doe,janedoe02,904321062,janedoe02@acme.com,2019-10-01,SUPERVISORY_ORGANIZATION-3-9244,Accountant, San Jose,Intern,918634631,
rajkumar01,Raj,Kumar,rajkumar01,903300421,rajkumar01@acme.com,2019-12-01,SUPERVISORY_ORGANIZATION-3-9244,CS-Customer Service#37486,OR,Part Time,918634631,Administrator
jamesanderson,James,anderson,jamesanderson,937393194,jamesanderson@acme.com,2019-11-25,SUPERVISORY_ORGANIZATION-3-9244,Computer Support Specialist,Brisbane,Full Time,918634631,IT
904221052,Aime,Jones,aimejones,948752281,aimejones@acme.com,2018-01-29,SUP_000012151,Corporate Services Manager,Shanghai,Full Time,987798693,Manager;Administrator'

You will need to create an instance of an import for each data type you wish to load. For example, if in step 4, while creating the job, you included two import objects in the request body - one for employee and another for an OU, you will need to send two distinct POST requests to the imports endpoint. The Bulk API will start processing the job as you send each import.

Here's an example of a successful response for the create import request.

  • The response tells you that the request was accepted indicating that it will be processed asynchronously.
  • The remaining_capacity field tells you how many more imports you can create for the same load type before you hit the maximum limit.
  • The report_url provides the URL to retrieve the results of your load. You can send a GET request to this endpoint to retrieve the records that errored out and the records that loaded with warnings.
Create Import Response
HTTP/1.1 202 ACCEPTED
Content-Type: application/json;charset=UTF-8
Cache-Control: private
{
  "message": "request received",
  "remaining_capacity": 4,
  "import_id": "4f917dd5-c8ee-4462-83e6-eeaf030da53a",
  "report_url": "/services/api/x/bulk-api/v1/imports/4f917dd5-c8ee-4462-83e6-eeaf030da53a"
}

# Step 6: Check Job Status

There are several ways to check the status of your job.

  • Callback from Cornerstone. If you are not familiar with the concept of callbacks, read General Guidelines - Callback.
  • Fetching job status using the /jobs/{job_id} endpoint.
  • Looking up status in the Bulk API History page within a Cornerstone portal.

If you included a callback URL in the request to create a job in step 4, you will receive a notification from Cornerstone at that URL when the job is completed. The request body of the POST contains the job_id and the job_status. The job_status will always have a value of 'completed' because Cornerstone only sends a notification when the job has been completed. Here's an example.

// Callback Request Body
{
    "job_id": "c8352637-bb1a-4960-ab91-e4794bdd7bea",
    "job_status": "Completed"
}

Once you get the notification of completion at your callback endpoint, you can use the job_id to fetch additional details about the job's status by sending a GET request to the /services/api/x/bulk-api/v1/jobs/{job_id} endpoint. You should replace the job_id URL parameter with the ID Cornerstone sent in the callback payload (this is the same job_id you received in the response to the create job request in step 4).

Here's an example cURL request you can use.

  • Replace corpname with your portal name.
  • Replace the job_id URL parameter with the ID Cornerstone sent in the callback payload (this is the same job_id you received in the response to the create job request in step 4).
  • Replace the bearer token in the Authorization header with the token you acquired in step 1.
# Get Job Status Request
curl -X GET \
  https://{corpname}.csod.com/services/api/x/bulk-api/v1/jobs/c8352637-bb1a-4960-ab91-e4794bdd7bea \
  -H 'Authorization: Bearer ZWRnZQ:9a690ba0d994f9073c6961a37ae398e2c9a47c91dcc2d52d72d5f6a71dd18f52'

Here's an example of a successful response. The response indicates the current status of each import in the job along with the number of records in the payload, the number of records loaded successfully, loaded with warnings, and failed to load due to errors.

# Get Job Status Response
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: private
{
  "jobId": "c8352637-bb1a-4960-ab91-e4794bdd7bea",
  "imports": [
    {
      "id": "4f917dd5-c8ee-4462-83e6-eeaf030da53a",
      "status": "Completed",
      "total_records": 5,
      "successful_records": 5,
      "successful_records_with_warnings": 0,
      "error_records": 0,
      "error_status": null
    }
  ]
}

The time a job takes to complete will vary based on the number of imports in your job, the timing of your imports, the number of records in each import, the number of fields you are loading in each import, and any other jobs that are queued.

# Step 7: Retrieve Import Results

The last step in the process is to retrieve the results of your load. You can do this by sending a GET request to the /services/api/x/bulk-api/v1/imports/{import_id}/report endpoint. Replace the import_id URL parameter with the import_id you received in the response to the job status request in step 6. This is the same ID that you used in the URL parameter in step 5.

Here's a cURL command you can execute to create an import to load employees.

  • Replace corpname with your portal name.
  • Replace the import_id url parameter with the value you received in the response to the job status request in step 6.
  • Replace the bearer token in the Authorization header with the token you acquired in step 1.
# Get Import Results Request
curl -X GET \
  https://{corpname}.csod.com/services/api/x/bulk-api/v1/imports/{import_id}/report \
  -H 'Authorization: Bearer ZWRnZQ:9a690ba0d994f9073c6961a37ae398e2c9a47c91dcc2d52d72d5f6a71dd18f52'

You should receive an HTTP 200 response with the content-type being text/csv. If your import does not contain any errors, the response payload will only contain the header row of your payload listing the fields you imported. If your import contains any records that had errors or warnings, you will see those records in the response body with flags indicating if the record was loaded along with errors and warnings where applicable.

A note regarding 'modification history'

The Bulk API only updates the employee and OU records if at least one field in the record within your payload differs from what is in the system currently. In other words, if all the fields for a record in your payload matches what's in the system, the Bulk API skips that record and moves onto the next record in your payload. Remember this while looking at the 'Modification History' of user and OU records in the system.

If you wish to narrow down your report to just warnings and/or errors, there are two additional endpoints you can use:

  • GET /services/api/x/bulk-api/v1/imports/{import_id}/report/errors
  • GET /services/api/x/bulk-api/v1/imports/{import_id}/report/warnings

Both these endpoints function similar to the /report endpoint except they only return the records with errors and warnings respectively.

  • If your job contained multiple imports, you will need to retrieve the results for each import separately.
  • Cornerstone retains import results and the actual import payloads for 30 days from the day the import was created.
  • You can download results only five times for a given import. Any attempts to call the /services/api/x/bulk-api/v1/imports/{import_id}/report endpoint more than that limit will result in HTTP 410 errors.