ZipRecruiter Partner Platform
Welcome to the ZipRecruiter Partner Program.
If you have any questions or concerns regarding the program or our documentation, please contact us or find more information at the following:
Web: https://www.ziprecruiter.com/ats-partners
Email: atsintegrations@ziprecruiter.com
Job Ingestion
Candidate Delivery
Disposition Feedback
Job API
The Job API is used by ZipRecruiter partners to post jobs to our network, and later, to close them.
One endpoint, https://api.ziprecruiter.com/partner/v0/job
can be used for basic CRUD operations:
- POST to create jobs
- GET to retrieve a job
- PUT to update a job
- DELETE to close a job
The body of the request must contain a json
representation of a job, described below.
Unicode in job_id
s
In the Job API's GET
, PUT
, and DELETE
operations, a job_id
is included in the URI.
If this job_id
has any characters that are not in the unreserved set, they must be UTF-8
encoded, then the bytes URL-encoded (percent-encoded)
Wikipedia.
(curl
does this automatically, perhaps your library of choice does as well.)
Authentication
Sample authorization. Make sure to replace
meowmeowmeow
in all examples with your API key.
# With shell, you can just pass the correct header with each request
curl https://api.ziprecruiter.com/partner/v0/job \
-H "Authorization: Basic meowmeowmeow" ...
Partner API uses Basic authentication to allow access to all endpoints. You will be provided an API key; please keep it secret.
Job API Operations
Post a job
The job is an json-encoded object in the content. The job model is described below.
The job (identified by job_id
) cannot already exist.
HTTP Request
POST https://api.ziprecruiter.com/partner/v0/job
curl https://api.ziprecruiter.com/partner/v0/job \
-H "Authorization: Basic meowmeowmeow" \
-X POST \
-H "Content-Type: application/json" \
-d @job.json
Parameters
The job is an json-encoded object in the content. The job model is described below.
Update a job
The job is an json-encoded object in the content. The job model is described below.
The job (identified its job_id
in the body) must already exist.
Note: updating a job always makes it active again, if it was previously closed.
HTTP Request
Either:
PUT https://api.ziprecruiter.com/partner/v0/job
PUT https://api.ziprecruiter.com/partner/v0/job/<JOB_ID>
If the latter url is used, the JOB_ID
in the url must match the one in
the body.
curl https://api.ziprecruiter.com/partner/v0/job \
-H "Authorization: Basic meowmeowmeow" \
-X PUT \
-H "Content-Type: application/json" \
-d @job.json
Get a specific job
This endpoint retrieves a specific job by id.
At present, this only serves to demonstrate that the job exists in our system.
curl "https://api.ziprecruiter.com/partner/v0/job/emlwcmVjcnVpdGVy-3215" \
-H "Authorization: Basic meowmeowmeow"
HTTP Request
GET https://api.ziprecruiter.com/partner/v0/job/<JOB_ID>
URL Parameters
Parameter | Description |
---|---|
JOB_ID | The ID of the job to retrieve. |
Close a Job
curl "https://api.ziprecruiter.com/partner/v0/job/emlwcmVjcnVpdGVy-3215" \
-H "Authorization: Basic meowmeowmeow" \
-X DELETE
This request closes a specific job.
HTTP Request
DELETE https://api.ziprecruiter.com/partner/v0/job/<JOB_ID>
URL Parameters
Parameter | Description |
---|---|
JOB_ID | The ID of the job to close |
Re-open a Job
To re-open a job, simply update it again.
(It is fine to PUT with exactly the same fields.)
The Job Model
The job is represented as a hash (mapping, dictionary) of fields,
encoded as JSON in the request or response body.
Fields are optional unless otherwise specified.
Do not include fields that have no value, when you create a new job (POST).
You may give a JSON null
value for a field you wish to erase (such
as apply_url
), in an update (PUT).
The job_id
and employer_id
fields are required, and must be unique in the partner's system.
The employer_name
should be given the first time a job is created by that employer, it's
optional after that.
A job location is required. You must always give country
, and then either:
postal_code
, orcity
andstate
, orlatitude
andlongitude
.
If you have more than one of the above, provide all that you have. (For remote jobs, you can give the location of the hiring company's headquarters.)
The job title
is required.
A job_type
is required; it can be one of specific set of values (an enum), see below.
A minimal sample job, in json:
{
"employer_id": "emlwcGFydG5lcg-042",
"employer_name": "Stenographica, Ltd",
"job_id": "emlwcmVjcnVpdGVy-3215",
"title": "Longhand",
"job_type": "contract",
"state": "NY",
"city": "New York",
"country": "US",
"description": "<div>We need someone to write things by hand, on paper, legibly, in cursive.</div>"
}
The basics are
- ​
job_id
- REQUIRED. unique job id in the partner's system. employer_id
- REQUIRED. unique employer id in the partner's system.employer_name
- texttitle
- REQUIRED. job title. text.job_type
- REQUIRED. enum, can be one of:full_time
,part_time
,contractor
,contract
,temporary
,other
country
- REQUIRED. enum, can be one of:US
,CA
,AU
city
state
- two- (or three-) letter ISO-3166-2 abbreviation (including Canada and Australia)postal_code
description
- text, minimum 25 chars. May include these HTML tags:p em i strong b br ol ul li hr div
Other fields:
compensation_min
,compensation_max
- positive fixed point numeric (integer acceptable)compensation_period
- enum, can be one of:annual
,monthly
,weekly
,daily
,hourly
compensation_currency
- enum, can be one of:USD
,CAD
,AUD
show_compensation
- boolean, whether salary range should be shown with job listingapply_url
- a url, used for external apply, or for automated delivery with some ATSlatitude
,longitude
- fixed point numbers, e.g.-106.81923
requisition_id
- string, unique requisition id of the job, which will be used during the candidate applications deliverycpc
- numeric input, optionally with decimal fractions, that represents cost per click e.g.12.49
partner_attributes
- json object, with key-value pairs where key must be a string, containing any partner specific attributes e.g.{"foo": "bar", "foo1": 1}
Job API Response
The response to a successful request is a json object, as the example shown at right.
{
"job": {
"job_id": "emlwcmVjcnVpdGVy-3215",
"title": "Longhand",
"job_type": "full_time",
"latitude": "40.71427000",
"longitude": "-74.00597000",
"city": "New York",
"state": "NY",
"country": "US",
"employer_id": "emlwcGFydG5lcg-042",
"employer_name": "Stenographica, Ltd",
"description": "<div>We need someone to write things by hand, on paper, legibly, in cursive.</div>"
},
"success": true
}
The GET
operation will also include the additional field job_status
(in addition to the job) with one of the values listed below:
posted
- the job is posted and livepending
- the job is yet to be posteddeleted
- the job is closed and removed from our search engine
Job API Change Log
- 210928 enumerated permitted HTML tags in job description
Questions API
This section provides API specification for the optional Questions to be attached to an existing job listing, sometimes known as screening or pre-screening questions — a short series of questions presented to your applicants during the application process.
Questions API Operations
The endpoint, https://api.ziprecruiter.com/partner/v0/job/<JOB_ID>/questions
can be used for basic CRUD operations.
URL Parameter
JOB_ID
- The ID of the job to which the Questions are attached.
Post Questions
The body of the request must contain a json
representation of the questions, described below.
The questions for the job (identified by JOB_ID
) cannot already exist.
curl https://api.ziprecruiter.com/partner/v0/job/emlwcmVjcnVpdGVy-3215/questions \
-H "Authorization: Basic meowmeowmeow" \
-X POST \
-H "Content-Type: application/json" \
-d @questions.json
HTTP Request
POST https://api.ziprecruiter.com/partner/v0/job/<JOB_ID>/questions
Parameters
The questions are json-encoded object in the content. The question model is described below.
Update Questions
The body of the request must contain a json
representation of the questions, described below.
The questions for the job (identified by JOB_ID
) may already exist, which will be overwritten.
curl https://api.ziprecruiter.com/partner/v0/job/emlwcmVjcnVpdGVy-3215/questions \
-H "Authorization: Basic meowmeowmeow" \
-X PUT \
-H "Content-Type: application/json" \
-d @questions.json
HTTP Request
PUT https://api.ziprecruiter.com/partner/v0/job/<JOB_ID>/questions
Get Questions
This endpoint retrieves the questions attached to the job specified by JOB_ID
.
curl "https://api.ziprecruiter.com/partner/v0/job/emlwcmVjcnVpdGVy-3215/questions" \
-H "Authorization: Basic meowmeowmeow"
HTTP Request
GET https://api.ziprecruiter.com/partner/v0/job/<JOB_ID>/questions
Delete Questions
This request deletes all the questions attached the job specified by JOB_ID
.
curl "https://api.ziprecruiter.com/partner/v0/job/emlwcmVjcnVpdGVy-3215/questions" \
-H "Authorization: Basic meowmeowmeow" \
-X DELETE
HTTP Request
DELETE https://api.ziprecruiter.com/partner/v0/job/<JOB_ID>/questions
The Question Model
A sample list of questions, in json:
[
{
"question_id": "pet_name",
"type" : "text",
"question" : "What is your pet's name?",
"required" : true
},
{
"question_id": "pet_age",
"type" : "text",
"format" : "integer",
"question" : "How old is your pet, in months?",
"min" : 1,
"max" : 250
},
{
"question_id": "pet_weight",
"type" : "text",
"format" : "decimal",
"question" : "What is your pet's weight, in kg?",
"min" : 0.01,
"max" : 250
},
{
"question_id": "pet_adopt_date",
"type" : "date",
"question" : "When was your pet adopted, or born into your care?",
"min" : "1970-10-20",
"format" : "YYYY-MM-DD"
},
{
"question_id": "pet_sleep_pref",
"type" : "select",
"question" : "Where does your pet like to sleep?",
"options" : [
{"value": "bed", "label": "My Bed"},
{"value": "floor", "label": "The Floor"},
{"value": "alone", "label": "Its Bed"},
{"value": "na", "label": "None of the Above"}
]
},
{
"question_id": "pet_nose_color",
"type" : "multiselect",
"question" : "What colors are present on your pet's nose?",
"options" : [
{"value": "0", "label": "Pink"},
{"value": "1", "label": "Brown"},
{"value": "2", "label": "Liver"},
{"value": "3", "label": "Yellow"},
{"value": "4", "label": "Green"},
{"value": "5", "label": "None of the Above"}
]
},
{
"question_id": "pet_photo",
"type" : "upload",
"question" : "Please submit your cutest photo of your pet.",
"required" : true
}
]
The questions are expressed as an array of objects, each object representing a question,
encoded as json
in the request or response body.
The following fields are available in all questions, regardless of type
.
All are required except for the required
field.
All fields should be text values unless specified otherwise.
question_id
A unique identifier for this question. This can just be the ordinal number of the question (i.e. 1, 2, 3...) if you don’t already have an identifier for your questions.
type
The question type, which determines the appearance of the question to your applicants, as well what other fields are required and optional for the question here. The following types are valid:
- text
- date
- select
- multiselect
- upload
Each type may have further required and allowed fields; see below for more information.
question
The question text shown to applicants. Simple HTML formatting, like paragraphs and emphasis, is permitted.
required
Boolean, Optional. If present and
true
, this question cannot be skipped by applicants, and is required in order for the application to be considered complete and delivered to you.
Supported Question Types
text
A free-form text entry with optional minimum and maximum text length requirements.
min
max
The minimum and maximum number of characters required to accept an answer.
integer and decimal
An entry restricted to numeric input, optionally with decimal fractions, as well as optional minimum and maximum value requirements. This is expressed as an extension of the text type.
type
- Must betext
format
- Must be eitherinteger
ordecimal
min
max
The minimum and maximum value required to accept an answer.
date
A date entry with optional earliest and latest date requirements.
min
max
The optional earliest and latest date accepted, in
YYYY-MM-DD
format. Applicants will be shown a datepicker that restricts them to this range.format
Required, must be
YYYY-MM-DD
. This is required in order to be clear that only one date format supported.
select and multiselect
A list of options where either only one may be selected, or any number of options may be selected.
options
An array of objects, each with both of the following fields:
label
- The option text shown to applicants.value
- The corresponding value, or option identifier that is returned to your ATS in answers, if applicable.
upload
A file upload entry. Files from applicants for upload questions are limited to PDF and well-known image and text document formats, and may be no larger than 10MB. No additional fields are required.
Questions API Response
The response to a successful request is a json object, as the example shown at right.
{
"success" : true,
"job_id" : "emlwcmVjcnVpdGVy-3215",
"questions": [
{
"question_id": "pet_name",
"type" : "text",
"question" : "What is your pet's name?",
"required" : true
},
{
"question_id": "pet_age",
"type" : "text",
"format" : "integer",
"question" : "How old is your pet, in months?",
"min" : 1,
"max" : 250,
"required" : false
},
{
"question_id": "pet_weight",
"type" : "text",
"format" : "decimal",
"question" : "What is your pet's weight, in kg?",
"min" : 0.01,
"max" : 250,
"required" : false
},
{
"question_id": "pet_adopt_date",
"type" : "date",
"question" : "When was your pet adopted, or born into your care?",
"format" : "YYYY-MM-DD",
"min" : "1970-10-20",
"required" : false
},
{
"question_id": "pet_sleep_pref",
"type" : "select",
"question" : "Where does your pet like to sleep?",
"options": [
{ "value": "bed", "label": "My Bed" },
{ "value": "floor", "label": "The Floor" },
{ "value": "alone", "label": "Its Bed" },
{ "value": "na", "label": "None of the Above" }
],
"required" : false
},
{
"question_id": "pet_nose_color",
"type" : "multiselect",
"question" : "What colors are present on your pet's nose?",
"options": [
{ "value": "0", "label": "Pink" },
{ "value": "1", "label": "Brown" },
{ "value": "2", "label": "Liver" },
{ "value": "3", "label": "Yellow" },
{ "value": "4", "label": "Green" },
{ "value": "5", "label": "None of the Above" }
],
"required" : false
},
{
"question_id": "pet_photo",
"type" : "upload",
"question" : "Please submit your cutest photo of your pet.",
"required" : true
}
]
}
Features API
This section provides API specification for adding optional Features to an existing job. Features are paid add-ons that enhance the distribution of a job.
Available Features
TrafficBoost
TrafficBoost provides increased placement for your job on ZipRecruiter.com, partner sites, and in job alert emails. Your job gets an increased ranking score, which boosts its visibility in search results. Apply a TrafficBoost, and your job is promoted for up to 30 days or until it receives the specified number of views.
TrafficBoost has three levels that specify the number of views a boosted job has before the boost expires:
single
: 100 views or 30 days before the boost expiresdouble
: 200 views or 30 days before the boost expirestriple
: 300 views or 30 days before the boost expires
Features API Operations
The endpoint, https://api.ziprecruiter.com/partner/v0/job/JOB-ID/features
can be used for basic CRUD operations.
URL Parameter
JOB_ID
- The ID of the job to which the Features are attached.
POST/UPDATE Features
Example call to add a Feature to the jobs. JOB_ID in the URL must be replaced by the Job_ID of the job that will have the feature applied.
curl https://api.ziprecruiter.com/partner/v0/job/JOB_ID/features \
-H "Authorization: Basic meowmeowmeow" \
-X POST \
-H "Content-Type: application/json" \
-d @features.json
Example json file to add a single trafficboost. "single" can be replaced with "double" or "triple" depending on desired level.
{
"features": {
"trafficboost": "single"
}
}
A POST/UPDATE request must be used to add a feature to an existing job. Once a TrafficBoost has been applied to a job, it cannot be removed or changed to another level.
The request must contain a valid payload as listed in the example and Features model.
GET Features
Example call to get the current status of Features on a job. JOB_ID in the URL must be replaced by the Job_ID of the job that the user wants to find the status of.
curl https://api.ziprecruiter.com/partner/v0/job/JOB_ID/features \
-H "Authorization: Basic meowmeowmeow" \
-X GET
A GET request can be used to find the current state of Features associated with the Job_ID in the request URL. This will list all possible features that can be associated with a job along with the status, with it being set to none if it is not set.
Features API Responses
A successful request will display success and the current TrafficBoost status.
{
"success": true,
"features": {
"trafficboost": "single"
}
}
A request may also go through, but the user may lack a valid payment method or other issues may occur in which the status will not change.
{
"success": true,
"features": {
"trafficboost": "none"
}
}
Example responses are shown to the right. A request will always show the status of the Features e.g. if a user has a valid job and permissions but the call fails, the TrafficBoost status will unchanged from what it previously was. If a job already has TrafficBoost applied and a request is made, it will display the current boost as changes cannot be made once they are applied.
The Features Model
An example json body in a request to add a double TrafficBoost:
{
"features": {
"trafficboost": "double"
}
}
The Features model is represented as a JSON object in the request body, as shown in the examples.
The required fields are features
and trafficboost
.
features
- contains a hash of the Features that a job can have.trafficboost
- the level of TrafficBoost that the user wants to apply.
Errors
The Partner API uses the following error codes:
Error Code | Meaning |
---|---|
400 | Bad Request -- Your request is invalid. |
401 | Unauthorized -- Your API key is wrong. |
500 | Internal Server Error -- We had a problem with our server. Try again later. |
503 | Service Unavailable -- We're temporarily offline for maintenance. Please try again later. |
Most any error you get from the API is a 400, you'll get back a body like the one shown at right.
{
"error": "Invalid Payload: Property is required: /job_id.",
"success": false
}
The error property will describe the nature of the problem, and can include, for example:
- record not found (GET, PUT, DELETE), or record already exists (POST)
- content is missing required field
- content has bad value for an enum field
Apply Webhook
ZipRecruiter aims to make the job posting process simple from end to end. This includes automated job posting from your website or Applicant Tracking System (ATS) platform, as well as delivering new job applications back to it.
Requirements
ZipRecruiter Apply Webhook requires the following:
You must set up a Job API integration (optionally XML Feed Import) for automatic job posting, in at least the minimal format. This allows us to post and unpost jobs in line with your ATS, as well as provides us with your ATS’s job identifiers so applicants can be delivered back to a specific job in your ATS. This feed does not need
url
tags; they will be ignored.You must provide an HTTPS endpoint which accepts a POST request in JSON format. This is a single URL which handles submissions for all of your jobs. You will need to let us know which content type you need when you furnish the URL for this endpoint.
Transport
The ZipRecruiter Apply Webhook makes an HTTPS POST to the endpoint of your choosing. We do not deliver to non-HTTPS services.
This endpoint must furnish a verifiable SSL certificate signed by a well-known Certificate Authority (such as those trusted by Mozilla). If your web browser can visit the endpoint without generating a security warning, we can support it.
Format
Example JSON Request
POST /job/apply HTTP/1.1
Host: yourhost.example.com
Content-Type: application/json
{
"response_id": "a39bd9a",
"job_id": "1000002”,
"name": "Tom Foolery",
"first_name": "Tom",
"last_name": "Foolery",
"email": "tf@example.org",
"phone": "+1 5555551942",
"resume": "JVBERi0xLjUKJb/3ov4KMiAwIG9iago8PCAvTGluZWFyaXplZCAxIC9MIDE3ODA3IC9IIFsgNjg3IDEyNiBdIC9PIDYgL0UgMTc1MzIgL04gMSAvVCA...jIxNgolJUVPRgo=",
"attributes": {
"tracking_code": "r2d2c3po"
},
"profile": {
"executive_summary": "Business Analyst with over 5 years of experience supporting business solution software and analyzing business operations.",
"mobile": "+1 555 555-1942",
"job_records": [
{
"start_date_precision": "YearMonthDay",
"position": "Business Analyst",
"description": "Analyzing data",
"start_date": "2012-09-01",
"end_date_precision": null,
"employer": "Toms",
"current": "1",
"end_date": null
},
{
"position": "Business Analyst",
"start_date_precision": "YearMonthDay",
"start_date": "2010-09-01",
"end_date_precision": "YearMonthDay",
"description": "Still Analyzing data",
"current": "0",
"employer": "Fooleries",
"end_date": "2012-05-01"
}
],
"text_resume": "BUSINESS ANALYST SAMPLE RESUME",
"city": "Santa Monica",
"state": "CA",
"postal_code": "90401"
}
}
The Content-Type
request header will be set to application/json
.
Response
Example JSON Response
{
"candidate_id": "44016-ksrm",
"job_id": "V-8632",
"additional_data": {
"summary": {
"application": {
"status": "success",
"error": null
},
...
}
}
}
A 200 OK
or 2xx HTTP response is considered a successful delivery. Other response
codes are considered unsuccessful, and are logged and handled, and may be retried automatically or manually.
For successful applications, the response body can include either:
application_id
orcandidate_id
andjob_id
This information will be stored on our side to support future API calls with these identifiers (such as the Hiring Signal API).
Any additional data can be included as part of the additional_data
field.
The basics are
application_id
- Partner's unique identifier for the job application, if anyjob_id
- Partner's unique identifier for the jobcandidate_id
- Partner's unique identifier for the jobseekeradditional_data
- A free-form object containing any additional details about the application
Fields
All field values are alphanumeric strings unless specified otherwise.
response_id
A unique identifier for the Job Application being transmitted, assigned by ZipRecruiter.job_id
The external identification for the Job as provided to ZipRecruiter with the Job API'sjob_id
or XML Feed'sreferencenumber
field. This is the Job ID your system uses.email
The Applicant’s email address.name
The Applicant’s full name as entered.first_name
The Applicant’s first name as entered or parsed.last_name
The Applicant’s last name as entered or parsed.phone
The Applicant’s phone number. Optional.However, when it’s part of the response, it will be of the format: +N NNNNNNNNNN
​resume
The Applicant’s attached resume, in PDF format.The file will be a base-64 encoded stream without whitespace.
attributes
Key/value pairs for custom behavior. See also the tag<partner_attributes>
in Feed Import. Optional.A JSON object, with custom string fields and string values (i.e., no nesting).
​answers
The Applicant’s interview answers, as an array of objects, if an interview was added to the job through your job feed. This is an advanced and optional, feature. If not in use, this field is omitted.profile
The jobseeker’s profile data. The data is either parsed from the jobseeker’s resume or entered from profile page. If there is no profile data, profile field is omitted. The fields are either string or array of objects.String fields: mobile, executive_summary, objective, text_resume, city, state, postal_code
Array of objects: job_records, education_records, achievement_records, license_certification_records, association_records
Signatures
You may optionally enable signatures for network requests from Apply Webhook.
After signatures are enabled, you must:
Verify the signature.
Check that the current time is within a few seconds of the timestamp in the request.
Benefits
Guarantee that payloads sent from Apply Webhook originate from ZipRecruiter.
Guarantee that payloads sent from Apply Webhook were not changed.
Prevent replay attacks.
Secret Exchange
Upon request, we will provide a secret string via a one-time access link to you, leveraging a secure delivery method. This secret will be shared within the same ZipRecruiter org. You should keep this secret safe from potentially malicious actors.
Signature
Full Signature Headers Example in HTTP Request
POST http://localhost/apply
Accept-Encoding: gzip
Authorization: Basic 123abc
User-Agent: Test-WWW-Mechanize/1.50
Content-Length: 22978
Content-Type: application/json
X-ZipRecruiter-Signature: c//+/Qo4wuJyzFENikeVOabKFXCJ5wLHzlTqkxHgZTk=
X-ZipRecruiter-Signature-Timestamp: 2021-02-12T15:49:55.214Z
X-ZipRecruiter-Signature-Version: v1; sha256
{"response_id": "1", "first_name": "John", "last_name": "Doe"}
Implementation Sample
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.Mac;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.security.NoSuchAlgorithmException;
import java.security.InvalidKeyException;
public class Main {
public static String encode(String key, String data) {
Mac sha256_HMAC = null;
try {
sha256_HMAC = Mac.getInstance("HmacSHA256");
}
catch(NoSuchAlgorithmException e) {
System.out.println("Something is wrong");
}
SecretKeySpec key_spec = new SecretKeySpec(key.getBytes(StandardCharsets.US_ASCII), "HmacSHA256");
try {
sha256_HMAC.init(key_spec);
}
catch(InvalidKeyException e) {
System.out.println("Something is wrong");
}
return Base64.getEncoder().encodeToString(sha256_HMAC.doFinal(data.getBytes(StandardCharsets.US_ASCII)));
}
public static void main(String[] args) {
String message = getMessage();
String secret = "my secret";
String signature = encode(secret,message);
String sent_signature = "c//+/Qo4wuJyzFENikeVOabKFXCJ5wLHzlTqkxHgZTk=";
System.out.println(sent_signature.equals(signature));
}
}
import hmac
import hashlib
import base64
message = getMessage()
secret = 'my secret'
signature = base64.b64encode(hmac.new(bytes(secret , 'ascii'), msg = bytes(message , 'ascii'), digestmod = hashlib.sha256).digest()).decode()
sent_signature = 'c//+/Qo4wuJyzFENikeVOabKFXCJ5wLHzlTqkxHgZTk=';
print(signature == sent_signature)
import (
b64 "encoding/base64"
"fmt"
"crypto/sha256"
"crypto/hmac"
)
func main() {
message := getMessage()
secret := "my secret"
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(message))
signature := b64.StdEncoding.EncodeToString(mac.Sum(nil))
sent_signature := "c//+/Qo4wuJyzFENikeVOabKFXCJ5wLHzlTqkxHgZTk="
fmt.Println(signature == sent_signature)
}
Once signatures are enabled and secrets have been exchanged, we will send 3 signature-related headers with every Apply Webhook delivery to your endpoint: "X-ZipRecruiter-Signature", "X-ZipRecruiter-Signature-Version", and "X-ZipRecruiter-Signature-Timestamp".
X-ZipRecruiter-Signature: <signature>
<signature>: String of characters returned by signature algorithm.
Example: c//+/Qo4wuJyzFENikeVOabKFXCJ5wLHzlTqkxHgZTk=
You can verify this signature as follows:
- Take the API Webhook payload. For example:
- {"response_id" : "1","first_name" : "John", "last_name" : "Doe"}
- Take the timestamp from X-ZipRecruiter-Signature-Timestamp:
- 2021-02-12T15:49:55.214Z"
- Concatenate timestamp and payload, separated by a dot (.). Note that the payload needs to be from the request body without altering it.
- 2021-02-12T15:49:55.214Z.{"response_id" : "1","first_name" : "John", "last_name" : "Doe"}
- Create a base64-encoded HMAC SHA256 hash out of timestamp.payload with your secret without breaking the encoded string into lines.
- c//+/Qo4wuJyzFENikeVOabKFXCJ5wLHzlTqkxHgZTk=
- Verify that the calculated signature is identical to the signature in X-ZipRecruiter-Signature with a case-sensitive equality check.
X-ZipRecruiter-Signature-Version: <version; comment>
<version; comment>: a version followed by an ignorable comment, separated by a semi-colon (;). The comment indicates the hashing algorithm. Versioning indicates our method for computing signatures.
Example: v1; sha256
X-ZipRecruiter-Signature-Timestamp: <timestamp>
import java.util.Calendar;
import java.time.Instant;
import java.time.Duration;
public class Main {
public static void main(String[] args) {
Instant sent_time = get_time();
Instant current_time = Calendar.getInstance().toInstant();
Duration duration = Duration.ofSeconds(5);
Instant max_window = sent_time.plus( duration );
System.out.println(current_time.isBefore(max_window));
}
}
from datetime import datetime, timedelta
sent_time = get_time()
current_time = datetime.now()
max_window = sent_time + timedelta(0,5)
print(current_time < max_window)
import (
"fmt"
"time"
)
func main() {
sent_time := get_time()
current_time := time.Now()
max_window := sent_time.Add(time.Second * time.Duration(5))
fmt.Println(current_time.Before(max_window))
}
<timestamp>: a date-timestamp in UTC conforming to RFC 3339.
Example: 2021-02-12T15:49:55.214Z
To prevent replay attacks, you should check that the timestamp in the header is within a few seconds of the current timestamp. Too large of a window size will result in false negatives (i.e., not catching a replay attack if one has happened). Too small of a window size will result in false positives (i.e., rejecting valid requests).
Caveat(s):
- Signatures are only supported for JSON data
Frequently Asked Questions
Do you provide a sandbox for testing?
Unfortunately, at the moment we do not have a sandbox for testing the integration. However, we can make arrangements to provide a test import and a few test applications if needed.
Do I need a URL for every job?
No. Please provide only one POST URL for the webhook, and the software receiving that POST can associate applications with jobs by the provided job_id field.
Can I add an interview or screening questions to my jobs?
This is an advanced feature. We support a standard interview format and a newly updated question format, as well as delivery of applicant answers through this webhook. If you are interested, please contact us. If your ATS platform supports interviews but doesn't support our format, please reach out. We may be able to help on a case-by-case basis. Also, please reach out if you intend to use the new question format.
Hiring Signals API
Introduction
This API permits ZipRecruiter partners to report hiring-related events regarding applications we have forwarded to them.
Version documented: v0
There is a single endpoint event
that accepts POST requests to
report hiring events.
The body of the request must contain a json
representation of the
event, described below.
Authentication
To authorize, use this code:
# With shell, you can just pass the correct header with each request
curl https://api.ziprecruiter.com/hiring-signal/v0/event \
-H "Authorization: Basic meowmeowmeow" ...
Make sure to replace
meowmeowmeow
with the properly encoded API key.
Partner API uses Basic authentication to allow access to all endpoints. You will be provided an API key; please keep it secret.
Operations
Post an event
The hiring event is an json-encoded object in the content.
The event model is described below.
POST https://api.ziprecruiter.com/hiring-signal/v0/event
curl https://api.ziprecruiter.com/hiring-signal/v0/event \
-H "Authorization: Basic meowmeowmeow" \
-X POST \
-H "Content-Type: application/json" \
-d @event.json
The Event Model
The hiring event is represented as a JSON object in the request body, as shown in the examples.
Fields are optional unless otherwise specified.
A minimum event, in json:
{
"zr_application_id": "681d065d",
"event": "contacted",
"event_timestamp": "2021-01-25T18:46:04Z"
}
A sample rejected event, with additional fields:
{
"zr_application_id": "681d065d",
"event": "rejected",
"status_name": "Knock-Out",
"status_group": "Rejection",
"reason": "automatic",
"event_timestamp": "2021-01-25T18:46:04Z",
"additional_data": {
"total_stages": 8,
"external_id": "681d065d",
"current_stage": 1,
"last_activity_at": "2022-04-13T07:59:37.048-07:00",
"disposition": "Knock-Out",
"milestone": "Application",
"applied_at": "2022-04-10T16:24:01.366-07:00"
...
},
"job_id": "V-8632",
"candidate_id": "44016-ksrm"
}
A sample hired event, with additional fields:
{
"zr_application_id": "681d065d",
"event": "hired",
"status_name": "Hired",
"event_timestamp": "2021-01-25T18:46:04Z",
"additional_data": {
"status": "Hired",
"sub_status": "Hired for Different Position",
"current_stage": 5,
"total_stages": 5,
"last_activity": "2021-01-25T18:49:14Z",
...
},
"job_id": "V-8632",
"candidate_id": "44016-ksrm"
}
One of the below listed identifiers is required. You must provide either:
zr_application_id
, orapplication_id
, orjob_id
andcandidate_id
The basics are
zr_application_id
- the identifer we sent with the applicationapplication_id
- partner's unique identifier for the application, if anyjob_id
- partner's unique identifier for the jobcandidate_id
- partner's unique identifier for the jobseeker
In addition, following fields are required.
event_timestamp
- REQUIRED. UTC time of the event in RFC3339 format, e.g. '2021-01-25T18:46:04Z'
One of the below listed descriptive fields is required. When possible, provide all of the following:
- status_name
- customer- or partner-defined name/label for the application's current status, unaltered. (It may be a reason a candidate declined or was rejected, or a step in the candidate hiring funnel)
- status_group
- customer- or partner-defined group name/label for the application's current status, unaltered, if any. If the status values (status_name
) are grouped into categories, then this field is the category. It may be a stage in the hiring funnel, if stages are further characterized by dispositions
- event
- enum, can be one of the following values:
event | meaning |
---|---|
received |
Default stage when an application is received and viewable in the ATS. |
viewed |
An application was viewed and/or reviewed by an employer but no action has been taken. |
contacted |
Candidate has either been contacted by phone, email, etc for an initial screening, or there is an intent to contact the candidate for screening. |
assessment |
Optional stage for more technical/skills jobs when a candidate will be given or has received a quiz, skills assessment, case study, test etc. |
interviewed |
Candidate has been scheduled or has completed an in-person, video, or phone interview. (Made it past the initial recruiter/HR screening). |
offered |
An offer of employment has been sent to a candidate. |
prehire |
The candidate is undergoing or will next require a screening check, such as background check, medical checks, reference checks, immigration validation, etc. |
hired |
The candidate has accepted an offer of employment. |
rejected |
The application has been rejected, knocked out of the system, or reassigned. |
unable_to_map |
Use this status only when you cannot find a corresponding status. When using this status, must provide status_name and/or status_group. |
Additional very helpful fields:
- additional_data
- a free-form object containing the partner's description of the application's current status. This might include stage names, numbers, timestamps, etc.
- reason
- enum. given with the rejected
event to categorize the rejection. one of:
reason | meaning |
---|---|
automatic |
Automatically rejected due to being knocked out by the system or not meeting the required criteria. |
closed |
The role was filled by another candidate, or closed. |
failed_checks |
The candidate failed background, pre-employment, etc checks. |
uncertified |
The candidate lacks a required license or certification. |
unresponsive |
The candidate was uncontactable, or did not respond. |
out_of_area |
Applicant is not in required area, or will not relocate. |
unavailable |
Applicant turned down the offer, or is not available in required time frame. |
unqualified |
Applicant was explicitly rejected for unspecified reason. |
consider_for_other_role |
Applicant was recommended for another role within the company. |
hired_for_other_role |
Applicant was rejected because they were already hired for another role within the company. |
other |
Any reason not covered by above. |
Event Response
The status 200 response to a successful request is a json object (encoded into the body), as the example shown at right.
{
"zr_application_id": "681d065d",
"success": true
}
Event Errors
If the given data does not pass validation, the response will have HTTP
status 400, and returned json object will have an errors
attribute:
an array of one or more objects describing validation errors.
{
"status": 400,
"errors": [{
"path": "\/body\/event",
"message": "Missing property."
}]
}
Sample errors:
- Missing required property
event
orreason
is not one of the enumerated values- Missing time zone on
event_timestamp
A status 404 response indicates the given zr_application_id
does not exist,
and the body makes that clear.
{
"status": 404,
"errors": [{
"path": "\/body\/zr_application_id",
"value": "56fe30a1",
"message": "Application does not exist"
}]
}
Change Log
- 2021-09-14 updated endpoint URL
Feed Import
Requirements
Note
Please provide the proper character encoding for your feed in the XML header. The Example Feed illustrates UTF-8, however this may or may not be the case for you. Please check with your system administrator if your need clarification. If you’re not specifying the proper encoding, your listings may appear with unusual characters.
A feed must consist of a <source>
tag containing one or more <job>
tags. All job content must be inside CDATA
sections or properly encoded with XML entities to avoid issues processing your XML feed. All XML tags are
case-insensitive unless otherwise specified.
Feeds must provide all currently open jobs, not jobs added since the last feed request. Jobs not included in your feed will be removed from your account, our search results, and our network.
The referencenumber provided for each job must be unique to that job. The referencenumbers of jobs found in your feed are compared to those on your ZipRecruiter account in order to determine what jobs should be added, removed, or updated.
Minimum Requirements for Jobs to be Delivered
​
referencenumber
Must be unique for each job and less than 64 characters.title
Must be at least 3 characters.company
The company name displayed on job postings.country
Acceptable values areUS
,CA
,AU
.city
andstate
Required ifpostalcode
is not provided.postalcode
Required ifcity
andstate
are not provided.description
Must be at least 25 characters.
You may include additional optional fields such as jobtype
, experience
, or interview_json
. If included, these fields must contain only the explicit values noted in this document, otherwise the job will be rejected.
Example Feed
<?xml version="1.0" encoding="UTF-8"?>
<source>
<!-- Optional Metadata Fields -->
<lastBuildDate>Wed, 27 Aug 2014 01:49:49 GMT</lastBuildDate>
<publisherurl>http://dev.ziprecruiter.com:4014</publisherurl>
<publisher>ZipRecruiter</publisher>
<!-- End Optional Metadata Fields -->
<job>
<referencenumber>a-unique-job-id-123</referencenumber>
<requisition_id>a-job-requisition-id-123</requisition_id>
<title>Auto Tech.</title>
<description><![CDATA[Hiring a skilled Auto Technician <b>ASAP</b>!]]></description>
<country>US</country>
<city>Santa Monica</city>
<state>CA</state>
<postalcode>90755</postalcode>
<company>Euro Car Care, Inc</company>
<date>2014-07-31T07:27:14</date>
<!-- Candidate Delivery Fields -->
<url><![CDATA[http://jobs.dev.ziprecruiter.com:4014/job/Auto-Tech/cc68d2f943/?source=ziprecruiter-feed]]></url>
<!-- Additional Optional Fields -->
<address>1615 Ocean Ave</address>
<jobtype>Full-Time</jobtype>
<accept_remote>0<accept_remote>
<experience>mid</experience>
<education>ged</education>
<compensation_interval>hourly</compensation_interval>
<compensation_min>24.00</compensation_min>
<compensation_max>48.00</compensation_max>
<compensation_currency>USD</compensation_currency>
<benefits>
<medical>1</medical>
<dental>1</dental>
<vision>0</vision>
<life_insurance>1</life_insurance>
<retirement_savings>0</retirement_savings>
</benefits>
</job>
<!-- Further <job>s follow... -->
</source>
Optional Metadata Fields
:
These fields may be included for feed-compatibility with other systems.
Candidate Delivery Fields
:
Normally only one of these Candidate Delivery Fields should be included, but both are shown in this example.
Additional Optional Fields
:
See Additional Optional Fields - Get More Exposure
Job Fields
Each <job>
tag represents one job listing. The following are the core fields for jobs. Each should be a single tag per
job, containing nothing but the content for that field. See the Example Feed for reference.
​referencenumber
Required. A unique alphanumeric reference ID for the job listing, up to 64 characters in length. Jobs exceeding this limit will be rejected from import.title
Required. The Job listing title.description
Required. Your full job description goes here. HTML markup is allowed and may be scrubbed for security, and must comply with our terms. We may use this field for indexing and snippet generation.country
Required. The two-letter ISO country code where the position is located.city
Required, unlesspostalcode
is present. The city where the position is located. When provided,state
must be provided as well. Where appropriate, this field can also mean a Locality, Town, or Municipality.state
Required, whencity
is present. The state or province where the position is located. Where appropriate, this field can also mean a Province, Region, or District.postalcode
Optional, but strongly encouraged overcity
andstate
. The Zip or Postal Code where the position is located. Providing a postal code will improve the quality of the job seekers matched with your position. We suggest only omitting this field if the position does not have a physical location.company
Optional. The company actually hiring for the position. If not present, the default hiring company selected in the feed import tool will be shown for this job. Avoid terms like "confidential" or "unknown," as vague company names are generally blacklisted by search engines.date
Optional, but recommended. The date provided for each job should indicate when the job was first published on your system. When not present, the time of import will be used. Date should be in either ISO8601 or RFC1123 format. That is,2016-07-31T07:27:14
orThu, 31 Jul 2014 07:27:14 PDT
.url
Optional. If present, candidates will be redirected to this URL when they wish to apply. Redirected candidates do not see the option to use ZipRecruiter Apply and are not tracked in your ZipRecruiter account. If you use an ATS integration or have a special partnership with ZipRecruiter, you may be instructed to use this field differently.requisition_id Optional. A job identifier from the partner not required to be unique on ZipRecruiter’s side, used for application delivery.
requisition_id
is used when a singlerequisition_id
on the partner's side maps to multiple jobs with uniquereferencenumber
s on ZipRecruiter's side. For example, this field is used when a partner's single requisition has multiple locations (ZipRecruiter’s API does not currently support this scenario).
Additional Optional Fields - Get More Exposure
We accept many more job fields that increase your search visibility as well as provide more detail to candidates in terms of compensation and requirements.
See Extended Job Fields for information on additional job fields we can support. Adding job address and compensation details is highly recommended for maximum visibility. These extended fields include:
Applicant Interview, or pre-screening
Feed Updates & Delivery
We require a full feed file, so that at any point in time you are specifying exactly which jobs you would like to have included on your account. A feed URL that only returns jobs which are new or changed since our last check will cause any prior jobs to be unposted from your account.
We’ll read and update your feed 4 times per day, approximately every 6 hours. It takes up to a few hours for updates to propagate fully and 24-48 hours for the updates to reach our job board partners. If your feed is preprocessed by a third party, this may introduce additional delays.
We currently accept files delivered via HTTPS or HTTP, and can support "Basic" authentication.
Feed Import Frequently Asked Questions
How do I close jobs when using the XML feed?
Remove the jobs you want to close from your XML feed, and they’ll be closed the next time the feed is updated.
Can I make changes to jobs using the ZipRecruiter web site?
Yes, you can update jobs posted via XML on the ZipRecruiter web site, but be aware that the next time your feed is updated via XML, your job will be updated to match what’s in the XML, which could overwrite any changes you made on the web site.
Can I "refresh" my old jobs by changing the dates?
No. If the values in <date>
are changed on a job that was previously imported, the Jobs folder will show the date when
the job was originally imported. However, we refresh the "posted on" date shown to jobseekers every 30 days.
Are duplicate entries allowed?
No, duplicate listings with the same job_id will cause the import to fail completely.
Extended Job Fields
All of the fields detailed in this section are optional, but can improve the jobseeker experience and candidate quality
for your listings. Everything listed here must be included inside the <job>
tag for each job to which it applies.
Examples can be found in the Example Feed.
Warning
If you use the exact same feed on other platforms, they may have trouble with these extended tags and require a copy of your feed without them.
Location
address
A street address to be displayed on your job listing. This can differentiate between locations in the same postal code. Do not include city, state, or postal code in this field.accept_remote
Indicates that the job can be performed remotely. If this tag is excluded, our search engine will attempt to infer it based on keywords in your job description, so we recommend that you include a value for every job to ensure accuracy. Acceptable values are1
or0
.
Employment and Compensation
jobtype
The employment type offered in this position. Acceptable values areFull-Time
,Part-Time
,Contractor
,Temporary
,Per Diem
, andOther
. This field is case-insensitive.employment_type
Available as an alias forjobtype
.compensation_interval
The payment interval that the following compensation values are listed in. For example $15 hourly or $65,000 annually. Acceptable values areAnnually
,Monthly
,Weekly
,Daily
, andHourly
. This field is case-insensitive.compensation_min; compensation_max
Numeric values. The minimum and maximum compensation offered per interval, for this position. This should be expressed with a period (ASCII 0x2E) as a decimal separator, and commas (ASCII 0x2C) for optional grouping separators.compensation_min
should be less or equal thancompensation_max
.compensation_has_commission
If present, with any content other than0
, adds the label "Plus Commission" alongside the listed compensation.compensation_currency
The currency that compensation is provided in, and that the above values should be labeled with. Acceptable values areUSD
,CAD
, andAUD
. If you providecompensaton_min
/compensation_max
, this field is required.
Benefits
<benefits>
<vision>1</vision>
<medical>1</medical>
<dental>1</dental>
<life_insurance>1</life_insurance>
<retirement_savings>1</retirement_savings>
</benefits>
Simple benefits info can be added to your job listing with a <benefits>
tag. This tag may contain any of the following
tags, which, if present with any content other than 0
, will indicate the corresponding benefit is offered with the
position. Acceptable benefits tags are vision
, medical
, dental
, life_insurance
, and retirement_savings
. Any
tags that are absent will not be shown as benefits on your job listing.
Applicant Requirements
resume_not_required
If present, with any content other than0
, this job will not require applicants to include a resumé to apply. Barring other requirements, this means you’ll accept applicants with only a minimum of name, email, and phone.experience
The experience level required for this position.Feed Value Description intern
Intern entry
Entry Level (0-2 years) mid
Mid Level (3-6 years) senior
Senior Level (7+ years) director
Director executive
Executive education
The education level required for this position.Feed Value Description ged
High School Diploma/GED assoc
Associates Degree undergrad
Bachelors Degree grad
Masters or Ph. D
Applicant Interview
Each job may have a series of interview questions for the candidate, and this interview can be specified in your job
feed. The optional <interview_json>
tag adds an interview to the job. This tag should contain JSON content, and
wrapping that JSON in a CDATA section is recommended. See The Question Model for details
on the expected JSON content.
If you have an ATS integration or other partnership, candidates' answers through interviews added by feeds or through your ZipRecruiter account may also be sent to your ATS platform.
Example Single-Question Interview, inside an XML snippet..
<interview_json>
<![CDATA[
[
{
"id": "color",
"type": "select",
"question": "What is your favorite color?",
"options": [
{"value": "blue", "label": "Blue"},
{"value": "red", "label": "Red"},
{"value": "green", "label": "Green"}
]
}
]
]]>
</interview_json>
Example Multi-Question Interview with grouping and conditionality, inside an XML snippet.
<interview_json>
<![CDATA[
[
{
"id": "ParentQID",
"type": "select",
"question": "Do you want to answer next question?",
"group_number":1,
"options": [
{ "value": "Yes", "label": "Yes" },
{ "value": "No", "label": "No" }
]
},
{
"id": "ChildQID",
"type": "select",
"question": "Answer this question?",
"group_number":1,
"condition" : {
"id" : "ParentQID",
"value": "Yes"
},
"options": [
{ "value": "1", "label": "one" },
{ "value": "2", "label": "two" }
]
}
]
]]>
</interview_json>
If the <interview_json>
tag is empty or missing, no changes to existing
questions/answers will be made.
To remove all questions from an existing job, send an empty json array as
the content of the tag, e.g.
<interview_json> <![CDATA[ [] ]]> </interview_json>
.
Partner Attributes
Each job may include Partner Attributes, arbitrary key/value data that may be used for ATS integrations or other custom
behaviors. Within <partner_attributes>
, tags of any name are allowed, and any value is accepted, but tags may not be
nested.
Example Partner Attributes.
<partner_attributes>
<tracking_code>r2d2c3po</tracking_code>
</partner_attributes>
References
JSON Schema
This following specifications are expressed as JSON Schema documents, which are used for input validation at ZipRecruiter, and can be used for output and testing validation. The current version of this schema can always be found at the URLs below.
The following tools can be used from a web browser to test a JSON document against a JSON Schema.
Be aware that JSON Schema is currently in an ongoing draft process, and tools may not support the most recent draft specification. Most schemas can be translated back to older drafts in order to support outdated tools.
JSON Schemas
Version V1
Version V2
- answers-schema-v2.json
- apply-webhook-schema-v2.json
- screener-questions-schema-v2.json
- profile-schema.json
Changelog
2023-06-14
- Added Feed Import Minimum Requirements
2023-04-27
- Removed No-longer-supported Job Performance API
2022-01-20
- Added Features API
2021-08-12
- Added Job Performance API
2021-04-14
- Added Apply Webhook, Hiring Signals API, Feed Import drafts
2020-12-14
- Added Questions API