Introduction
The Eploy REST API services are a collection of single responsibility endpoints, allowing for authorised external applications to read data from an Eploy system.
Authentication
All requests to the API must be accompanied with a valid form of authorization that can be linked to an API Key.
For the vast majority of endpoints, this authorization is in the format of an OAuth2 Bearer token, which allows us to ensure that requests to the system are legitamate.
How to create an API Key
To create an API Key, log in to the backend system and navigate to the Admin->Security Settings->API Keys.
Create a new ‘REST API’ key, and provide the record with a Title and a User that the API key will be authenticated as.
The API Key will only be able to access information within the same Admin Modules as the selected User’s general permissions.
An IP address must be assigned to the User for the API key to work. IP wildcards ( * ) are supported, allowing for xx.xx.* or * to be accepted.
Once saving this record, you will be able to add API Permissions to the API Key, which will limit what the API Key is able to access.
How to generate an OAuth2 Bearer Token
Once an API Key has been created, a bearer token should be generated by sending a POST request with the /api/token endpoint.
In order to do this, the user must pass the client_id and client_secret created by the API Key in the following format.
POST https://systemname.eploy.net/api/token
~Request Body~
{
"grant_type": "client_credentials",
"client_id": "CLIENTID",
"client_secret": "CLIENTSECRET"
}
The OAuth2 grant type must be set to client_credentials to generate a bearer token using client_id and client_secret
Providing that the request is valid, the endpoint will respond with a bearer token which should be provided in subsequent requests.
The response will include the time that the bearer token will remain valid, following this, the token will no longer be accepted and a new token must be generated.
{
"access_token": "TOKENTOKENTOKENTOKEN",
"token_type": "Bearer",
"expires_in": 599,
"scope": "actions.insert,actions.read,actions.update"
}
OAuth2 Scopes
OAuth Scopes can also be used to limit the access of a specific API Key.
As an optional request parameter, passing in a comma seperated list of ‘scope’ will further reduce the endpoints that the generated API key can access.
The user will only be able to select scopes that have been approved as part of the API key’s security settings.
POST https://systemname.eploy.net/api/token
~Request Body~
{
"grant_type": "client_credentials",
"client_id": "CLIENTID",
"client_secret": "CLIENTSECRET",
"scope": "actions.insert,applications.insert"
}
POST https://systemname.eploy.net/api/token
~Request Body~
{
"access_token": "TOKENTOKENTOKENTOKEN",
"token_type": "Bearer",
"expires_in": 599,
"scope": "actions.insert,applications.insert"
}
A full list of Scopes are as follows
actions.insert
actions.read
actions.update
applications.insert
applications.read
applications.update
candidates.insert
candidates.read
candidates.update
companies.insert
companies.read
companies.update
contacts.insert
contacts.read
contacts.update
export.insert
files.read
payrates.read
paysalaries.read
placements.insert
placements.read
placements.update
purchaseorders.read
timesheets.insert
timesheets.read
timesheets.update
timesheets.delete
users.insert
users.read
users.update
vacancies.insert
vacancies.read
vacancies.update
vacancytemplates.read
workflows.read
How to use a OAuth2 Bearer Token
Once a valid bearer token has been generated and returned to the user, the token must then be passed in to subsequent requests.
To do this, an authorisation header should be passed in the following fomat.
Authorization: Bearer TOKENTOKENTOKENTOKEN
Rate Limiting
The Eploy REST API has a Rate Limit of:
10 requests per second.
1000 requests per day (reset at 00:00:00 each day).
The daily rate limit can be increased on request.
All responses to authenticated requests will contain the following Rate Limit tracking headers.
X-RateLimit-Total-Second-Limit
X-RateLimit-Remaining-Second
X-RateLimit-Total-Day-Limit
X-RateLimit-Remaining-Day
Example Requests and Responses
GET Record
To retrive a specific record from an endpoint, then the record ID should be passed as part of a get request.
Example Request
~HEADERS~
Authorization: Bearer jkajahafafjafjaaf
GET https://systemname.eploy.net/api/placements/1
Example Response
~Response Code~
200 - OK
~Response Body~
{
"PlacementId": 1,
"StartDate": "2021-01-21T18:42:00",
"EndDate": null,
"City": null,
"WeekStartDay": {
"Id": "1",
"Description": "Sunday",
"Reference": null
},
"User": {
"Id": 1,
"Url": "https://systemname.eploy.net/api/users/1"
},
"Authoriser": null,
"PlacementStatus": {
"Id": "6",
"Description": "Onboarding Checked - Candidate Ready",
"Reference": null
},
"Vacancy": {
"Id": 7,
"Url": "https://systemname.eploy.net/api/vacancies/7"
},
"Candidate": {
"Id": 5026,
"Url": "https://systemname.eploy.net/api/candidates/5026"
},
"PurchaseOrder": 0,
"NominalCode": null,
"Department": {
"Id": null,
"Description": "0",
"Reference": null
},
"InvoiceFrequency": null,
"SplitInvoiceMethod": null,
"RateCurrency": {
"Id": "1",
"Description": "Britain (United Kingdom), Pounds",
"Reference": "GBP"
},
"ChargeCurrency": {
"Id": "1",
"Description": "Britain (United Kingdom), Pounds",
"Reference": "GBP"
},
"Salary": {
"Salary": 15000.0,
"SalaryInterval": {
"Id": "5",
"Description": "Yearly",
"Reference": "5"
},
"HoursPerWeek": 37.5,
"ChargeToClient": 0.0,
"PaySalaryItem": null
},
"WorkingHours": null,
"NoticeRequired": null,
"ReportTo": null,
"ReportWhere": null,
"ReportTime": null,
"ReasonForLeaving": null,
"ReasonForLeavingComments": null,
"Comments": null,
"RatesDescription": null,
"PayRate": null,
"Creator": null,
"Modifier": {
"Id": 1,
"Url": "https://systemname.eploy.net/api/users/1"
},
"CreationDate": "2015-12-01T18:42:55.623",
"ModificationDate": "2020-02-06T10:10:14",
"Questions": "https://systemname.eploy.net/api/placements/1/questions"
}
Search for Record
Searching for specific criteria can be done by communicating with the search endpoint of a given record.
Paging
Paging allows a large response to be split into smaller chunks; currently there is a maximum limit of 100 records per page (this has been increased to 1000 for Version47+). To request a specific page, or alter the available paging options from default, a Paging object can be included in the search request body. Options for paging include RequestedPage(the index of the page to return, starting at 1) and RecordPerPage which defaults to 20, and cannot be increased over 100 (or 1000 for version 47+).
All search responses include additional metadata: TotalRecords, TotalPages and the CurrentPage. From this it can be determined that while the CurrentPage < TotalPages there are more pages available. To get the next page, simply increment the RequestedPage value and repeat the request.
Filters
Filters can be passed as part of the body to search a particular record type for items which match the defined criteria.
Each filter is made up of a Route, a Value, and an Operation.
Regular Filters
The Route is a period seperated path to the response item to search by, for example, if the user wanted to search by a candidate ID, then the route would be “candidate.candidateid”.
The Value is what you are filtering each record against.
The Operation is how the value equality should be assessed, for example only records where the route is “GreaterThan” the value provided. The operation can be used in either numeric or string format, the following table indicates what values are accepted with the corresponding numeric operation value.
Accepted search operations are dependent on the data type of the field being filtered (i.e. the Route):
Field Data Type |
Example Value |
Accepted Operations |
---|---|---|
Text |
“abc“ no value required for IsEmpty / IsNotEmpty |
Equals (1), NotEquals (2), Contains (3), NotContains (4), IsEmpty(7), IsNotEmpty (8 ) |
Numeric |
0 |
Equals (1), NotEquals (2), GreaterThan (5), LessThan (6) |
Boolean |
true |
Equals (1) |
Date / DateTime |
“2015-12-01T18:00:00.00Z“ no value required for IsEmpty / IsNotEmpty |
Equals (1), NotEquals (2), Contains (3), NotContains (4), IsEmpty(7), IsNotEmpty (8 ) |
Option / ID |
123 |
Equals (1), NotEquals (2) |
Routes from other endpoints can also be used, providing that a link between the items can be made.
For example, Applications could be searched by the Candidate having a Driving License (candidate.drivinglicence).
Custom Questions can also be searched using the same format, with the keyword ‘Questions’ and the Question ID being used as part of the route to detemine what to filter by.
A question search must begin with the relecvant record type at the start of the route. For example “candidate.question.1”, providing that Question ID 1 is a member of Candidate.
Nested Filters
Some filters, such as Address1 and Rates have been wrapped in the API response to increase readability. In order to search for these fields, the filter must not include the wrapping object.
For example, Candidate.Address1, instead of Candidate.Address.Address1.
Exported Filters
A filter can also be passed to determine if a record has been exported by a particular export package within the system. To do this you can use the HasBeenExportedBy filter with the following routes on applicable endpoints:
-
Application.HasBeenExportedBy
-
Candidate.HasBeenExportedBy
-
Contact.HasBeenExportedBy
-
Company.HasBeenExportedBy
-
Placement.HasBeenExportedBy
-
Timesheet.HasBeenExportedBy
-
Vacancy.HasBeenExportedBy
Alternatively, instead of searching the endpoints for successfuly exported records, the /export/search endpoint can be used to retrieve data about all logged exports, including failed entries and records which may have been exported on multiple occasions. This can be used to find all data exported after a given date or for a particular export for example. This endpoint also allows filter routes on the following export fields:
-
Export.ExportPackageID (int)
-
Export.Success (bool)
-
Export.DateExported (datetime)
-
Export.ExternalID (string)
Unless filtered appropriately the results of this may return multiple entries for a given record and/or unsuccessful entries
File Search by RecordID
Files returns records linked to different records using a combination of RecordID and RecordTypeID, if using the filter route of File.RecordID, it is compulsory to also include a filter route of File.RecordTypeID to ensure the RecordID matches the desired record. Here are some common record types which can have files:
Record |
RecordTypeID |
---|---|
Company |
1 |
Contact |
2 |
Candidate |
3 |
Vacancy |
4 |
Placement |
11 |
Application |
18 |
An example file search request to find all files for Candidate 1:
~HEADERS~
Authorization: Bearer jkajahafafjafjaaf
POST https://systemname.eploy.net/api/files/file/search
~Request Body~
{
"Filters": [
{
"Route": "File.RecordID",
"Value": 1,
"Operation": "Equals"
},
{
"Route": "File.RecordTypeID",
"Value": 3,
"Operation": "Equals"
}
}
Response Blocks
Response Blocks are a whitelist of the response values that the user wants back.
If no Response Blocks are passed then only the ID of the record will be sent back.
Example Request
~HEADERS~
Authorization: Bearer jkajahafafjafjaaf
POST https://systemname.eploy.net/api/placements/search
~Request Body~
{
"Paging": {
"RecordsPerPage": 10,
"RequestedPage": 1
},
"Filters": [
{
"Route": "Candidate.EmploymentStatus",
"Value": 1,
"Operation": "Equals"
},
{
"Route": "Placement.Question.43",
"Value": true,
"Operation": "Equals"
},
{
"Route": "Placement.HasBeenExportedBy",
"Value": [ 1, 2 ],
"Operation": "NotEquals"
}
],
"ResponseBlocks":[
"PlacementID",
"City",
"Candidate"
]
}
Example Response
~Response Code~
200 - OK
~Response Body~
{
"Records": [
{
"PlacementId": 4,
"City": null,
"Candidate": {
"Id": 2969,
"Url": "https://systemname.eploy.net/api/candidates/2969"
}
},
{
"PlacementId": 5,
"City": {
"Id": "1",
"Description": "(UTC+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna",
"Reference": "1"
},
"Candidate": {
"Id": 2046,
"Url": "https://systemname.eploy.net/api/candidates/2046"
}
},
{
"PlacementId": 6,
"City": null,
"Candidate": {
"Id": 4126,
"Url": "https://systemname.eploy.net/api/candidates/4126"
}
}
],
"TotalRecords": 9,
"CurrentPage": 1,
"TotalPages": 3
}
Create Record
To create a new record, then the record ID should be passed as part of the POST request, and the request body should contain all information to be added to the system.
A complete request with all fields is not required, and a pre-creation validation check will ensure that all required fields are populated. Failing this, an error will be returned.
Some request properties require that another property is also passed in as part of the request body. Should these ‘parent’ properties not be provided, then the request will fail validation with a helpful error message being returned.
Example
Example Request
~HEADERS~
Authorization: Bearer jkajahafafjafjaaf
POST https://systemname.eploy.net/api/placements
~Request Body~
{
"StartDate": "2020-04-23T13:48:16.488Z",
"EndDate": "2020-04-23T13:48:16.488Z",
"CityId": 1,
"WeekStartDayId": 1,
"UserId": 1,
"AuthoriserId": 1,
"PlacementStatusId": 1,
"VacancyId": 1,
"CandidateId": 1,
"PurchaseOrder": 4,
"NominalCodeId": 1,
"DepartmentId": 1,
"InvoiceFrequencyId": 1,
"SplitInvoiceMethodId": 1,
"RateCurrencyId": 1,
"ChargeCurrencyId": 1,
"Salary": {
"Salary": 40000,
"SalaryIntervalId": 1,
"HoursPerWeek": 35,
"ChargeToClient": 20,
"SalaryBandId": 8
},
"WorkingHours": "WorkingHours",
"NoticeRequired": "NoticeRequired",
"ReportTo": "ReportTo",
"ReportWhere": "ReportWhere",
"ReportTime": "ReportTime",
"ReasonForLeavingId": 1,
"ReasonForLeavingComments": "ReasonForLeavingComments",
"Comments": "Comments",
"RatesDescription": "RatesDescription",
"PayRateId": 1
}
Example Response
~HEADERS~
Location: https://systemname.eploy.net/api/placements/80
~Response Code~
201 - Created
~Response Body~
{
"Id": 80,
"Url": "https://systemname.eploy.net/api/placements/80"
}
Update Record
To update a specific record, then the record ID should be passed as part of the PATCH request, and the request body should contain the information to be updated in the system.
A complete request with all fields is not required, and a pre-update validation check will ensure that all required fields are populated. Failing this, an error will be returned.
Some request properties require that another property is also passed in as part of the request body. Should these ‘parent’ properties not be provided, then the request will fail validation with a helpful error message being returned. Additionally, in some cases when a parent is provided, but the child is not, then the child field would be nullified as part of the record. For example, updating Address Line 1 would nullify Address Line 2 of the existing record (providing that it is not passed in on the request).
Example
Example Request
~HEADERS~
Authorization: Bearer jkajahafafjafjaaf
PATCH https://systemname.eploy.net/api/placements/80
~Request Body~
{
"StartDate": "2020-04-23T13:48:16.488Z",
"EndDate": "2020-04-23T13:48:16.488Z",
"CityId": 1,
"WeekStartDayId": 1,
"UserId": 1,
"AuthoriserId": 1,
"PlacementStatusId": 1,
"VacancyId": 1,
"CandidateId": 1,
"PurchaseOrder": 4,
"NominalCodeId": 1,
"DepartmentId": 1,
"InvoiceFrequencyId": 1,
"SplitInvoiceMethodId": 1,
"RateCurrencyId": 1,
"ChargeCurrencyId": 1,
"Salary": {
"Salary": 40000,
"SalaryIntervalId": 1,
"HoursPerWeek": 35,
"ChargeToClient": 20,
"SalaryBandId": 8
},
"WorkingHours": "WorkingHours",
"NoticeRequired": "NoticeRequired",
"ReportTo": "ReportTo",
"ReportWhere": "ReportWhere",
"ReportTime": "ReportTime",
"ReasonForLeavingId": 1,
"ReasonForLeavingComments": "ReasonForLeavingComments",
"Comments": "Comments",
"RatesDescription": "RatesDescription",
"PayRateId": 1
}
Example Response
~Response Code~
200 - OK
DELETE Record
To remove a specific record from an endpoint, then the record ID should be passed as part of a delete request.
Example Request
~HEADERS~
Authorization: Bearer jkajahafafjafjaaf
DELETE https://systemname.eploy.net/api/candidates/30/employment/1504
Example Response
~Response Code~
200 - OK
Mark Records as Exported
To mark a record as exported, the record ID(s) should be passed to the Export endpoint with an ExportPackageID to mark them against. The ExportPackageID is an internal record to the eploy system and cannot be created or modified through the API; it must be inserted by Eploy to allow users of the API to log exports correctly.
The External Reference of the record can also be passed in as part of this request if there is an alternative identifier; e.g. the ID of the record in the location it is being exported to.
Example Request
~HEADERS~
Authorization: Bearer jkajahafafjafjaaf
POST https://systemname.eploy.net/api/export
~Request Body~
{
"ExportPackageId": 7,
"VacancyIds": [
{
"Id": 1,
"ExternalReference": "Vacancy1"
},
{
"Id": 2,
"ExternalReference": "Vacancy2"
},
{
"Id": 3,
"ExternalReference": "Vacancy3"
},
],
"ApplicationIds": [
{
"Id": 1001,
"ExternalReference": "DaveApplication"
}
]
}
Recruitment Workflows
Application records link to recruitment workflows and the response model contains a “Workflow” object. This contains information about the current state of the application within a workflow.
"Workflow": {
"StageType": "Applications",
"Stage": {
...
},
"SubStage": {
...
"Description": "Application Stage",
},
"Status": {
"Id": "1",
"Description": "Website Application To Review",
"Reference": "",
"ArchiveDate": null,
"ParentId": null,
"OptionType": {
"Id": 49,
"Url": "https://devtest42demo.eploy.net/api/options/dropdown/49",
"Description": "Vacancy Application Status",
"Type": "dropdown"
}
}
}
"Workflow": {
"StageType": "Actions",
"Stage": {
...
},
"SubStage": {
...
"Description": "Complete",
},
"Status": {
"Id": "21",
"Description": "Failed",
"Reference": "",
"ArchiveDate": null,
"ParentId": null,
"OptionType": {
"Id": 154,
"Url": "https://devtest42demo.eploy.net/api/options/dropdown/154",
"Description": "Action Outcomes",
"Type": "dropdown"
}
}
}
"Workflow": {
"StageType": "Placements",
"Stage": {
...
},
"SubStage": {
...
"Description": "Placement Created",
},
"Status": {
"Id": "4",
"Description": "Offer & Contract Accepted",
"Reference": "",
"ArchiveDate": null,
"ParentId": null,
"OptionType": {
"Id": 91,
"Url": "https://devtest42demo.eploy.net/api/options/dropdown/91",
"Description": "Placement Status",
"Type": "dropdown"
}
}
}
Each stage in a workflow can either be a StageType of “Applications”, “Actions”, or “Placements”. Stage, SubStage and Status all share the same model as other related option values and will contain as a minimum an ID and Description describing the current Stage, SubStage within that Stage, and the Status relevent to that Stage. The Status for Application stages will be of type “Vacancy Application Status”, for Action stages “Action Outcomes” and for Placement stages “Placement Status”; this can also be determined “OptionType” metada within the Status object returned.
"Status": {
"Id": "1",
"Description": "Website Application To Review",
"Reference": "",
"ArchiveDate": null,
"ParentId": null,
"OptionType": {
"Id": 49,
"Url": "https://devtest42demo.eploy.net/api/options/dropdown/49",
"Description": "Vacancy Application Status",
"Type": "dropdown"
}
}
Some stages link to another record being created in relation to the application in the workflow such as Action and Placement stages, it is possible for an application to be within these stages without yet creating the record it links to. In this case, the Status would be null and the SubStage would indicate the current state within the Stage.
"SubStage": {
"Id": "1",
"Description": "Action Not Created",
"Reference": "MIS",
"ArchiveDate": null,
"ParentId": null,
"OptionType": {
"Id": 183,
"Url": "https://devtest42demo.eploy.net/api/options/dropdown/183",
"Description": "Application Sub Stages",
"Type": "dropdown"
}
}
"SubStage": {
"Id": "7",
"Description": "Placement Not Created",
"Reference": "MIS",
"ArchiveDate": null,
"ParentId": null,
"OptionType": {
"Id": 183,
"Url": "https://devtest42demo.eploy.net/api/options/dropdown/183",
"Description": "Application Sub Stages",
"Type": "dropdown"
}
}
A full list of Application SubStages is as follows:
Substage ID |
Description |
Record Type |
---|---|---|
1 |
Action Not Created |
Action |
2 |
Unconfirmed |
Action |
3 |
Confirmed |
Action |
4 |
Declined |
Action |
5 |
Complete |
Action |
6 |
Placement Not Created |
Placement |
7 |
Placement Created |
Placement |
8 |
Application Stage |
Application |
9 |
Talent Pool |
Pre-Applications |
10 |
Long List |
Pre-Applications |
11 |
Not Suitable |
Pre-Applications |
Search Option Values
To search for an option value, a request can be made to /api/options/{optiontype}/{optionid}/search with {optiontype} either being dropdown or custom depending on the type being searched.
Example Request
~HEADERS~
Authorization: Bearer jkajahafafjafjaaf
POST https://systemname.eploy.net/api/options/dropdown/36/search
~Request Body~
{
"Paging": {
"RecordsPerPage": 100,
"RequestedPage": 1
},
"Filters": [
{
"Route": "Option.ID",
"Value": 56,
"Operation": "Equals"
},
{
"Route": "Option.Description",
"Value": "Guinea",
"Operation": "Contains"
}
],
"ResponseBlocks": [
"ID",
"Description",
"Reference"
]
}
Example Response
~Response Code~
200 - OK
~Response Body~
{
"Records": [
{
"Id": "56",
"Description": "Equatorial Guinea",
"Reference": "GQ"
}
],
"TotalRecords": 1,
"CurrentPage": 1,
"TotalPages": 1
}
Add Option Values (v43+ only)
To add an option value, a request can be made to /api/options/{optiontype}/{optionid}/ with {optiontype} either being dropdown or custom depending on the type being searched.
Example Request
~HEADERS~
Authorization: Bearer jkajahafafjafjaaf
POST https://systemname.eploy.net/api/options/dropdown/36/
~Request Body~
{
"Description": "A user description",
"Reference": "An internal reference value",
"IsActive": true,
"ParentId": 2
}
Example Response
~Response Code~
201 - Created
~Response Body~
{
"Id": 3,
"Url": "http://localhost/eploy/options/dropdown/29/3"
}
Update Application Stage / Status (v44+ only)
As part of version 44, an application’s stage and status within that stage can be set when creating / updating an application.
Example Request
~HEADERS~
Authorization: Bearer jkajahafafjafjaaf
POST https://systemname.eploy.net/api/applications
~Request Body~
{
... Normal Create Applications Request ...
"Workflow": {
"StageID": 1,
"StatusID": 2,
}
}
Example Response
~Response Code~
201 - Created
~Response Body~
{
"Id": 123,
"Url": "https://systemname.eploy.net/api/applications/123"
}
When passing a Workflow object, the StageID must be provided. However, the StatusID may be optional depending on the type of stage the application is moving to.
Moving to an Application Stage
When moving to an application to a stage with an “application” type:
-
If the application stage has statuses set, then the StatusID field must be passed as part of the request.
-
If the application stage does not have statuses set, then the StatusID field is optional.
Moving to an Action Stage or Placement Stage
When moving to an application to a stage with an “action” or “placement” type:
-
Then the status ID must not be provided as part of the request.
Troubleshooting
401: Authorisation has been denied for this request
This means that the credentials used do not have permission to access the resource requested.
To check what permissions a particular credential has, it’s important to note that the API operates with permissions on two layers:
-
the user that is associated with a particular set of credentials must have permission
-
the credentials can be restricted to a sub-set of endpoints (allowing it to share a user with other keys and allow different access). When requesting an access token, a restricted set of scopes can be requested which will limit access just for the life of that token; scopes default to all allowed by the credentials if none specified when requesting the access token.
The permissions can be checked by navigating to Security → API Keys and selecting the api key in question. The endpoints enabled for that key, as well as read/write access is determined by clicking the “Add” button and/or editing the ones selected in the grid. The “Run as” user can be checked in Admin → Users selecting the user and going to the General Permissions tab.