Job Sync API guide

Start using the Job Sync API to submit and expire job postings.


Before you start

Use of this API is gated by a developer agreement. Contact marketplacesupport@indeed.com to request access before attempting to develop against this API.

Guidelines:

Follow these steps to get a client ID for your application and authenticate using an OAuth token generated by an Indeed account. You will receive an access token (lasts 1h), and a refresh token (lasts 60d).

All requests should be directed to:

POST https://apis.indeed.com/graphql
Note: The POST method must be used for all queries and mutations. The GET method is not supported.

The query, variable inputs, and an optional operation name should be included in the POST body:

{
  "query" : "..." ,
  "operationName" : "..." ,
  "variables" : { "myVariable" : "someValue" , ... }
}

Submit a job posting

createSourcedJobPostings(input: CreateSourcedJobPostingsInput): CreateSourcedJobPostingsPayload

Submit the entire payload with updated fields using the same jobPostingId, sourceName, and OAuth client ID.

Send all fields. The call will fail if fields are missing.

Example createSourcedJobPostings request

mutation {
    createSourcedJobPostings(input: {
        jobPostings: [{
            body: {
                title:"title 1"
                description:"description 1"
                location: {
                    country: "US"
                    cityRegionPostal: "Syracuse, New York 13209"
                }
                benefits: []
             }
            metadata: {
                jobSource: {
                    companyName: "Company"
                    sourceName: "Source"
                    sourceType: "Employer"
                }
                jobPostingId: "JobId1"
                datePublished: "2023-01-02T12:00Z"
                url:"http://example.com/careers/job1.html"
            }
        }]
    }) {
        results {
            jobPosting {
                sourcedPostingId
            }
        }
    }
}

Expire a job posting

expireSourcedJobsBySourcedPostingId(input: ExpireSourcedJobsBySourcedPostingIdInput!): ExpireSourcedJobsBySourcedPostingIdPayload

Expire a posted job using the sourcedPostingId that was returned when the job was submitted.

Indeed will not verify if the job exists before sending back a response, so the response will always be ACCEPTED.


Back to top

GraphQL schema


scalar CountryCode
scalar CurrencyCode
scalar DateTime
scalar Duration
scalar EmailAddress
scalar Int64
scalar PhoneNumber
scalar WebUrl

type Mutation {
  """
  Submit a job posting for ingestion into Indeed.

  Successful responses indicate that Indeed received the job posting. It will be indexed and available (pending spam and fraud detection) at a later time (minutes to hours).

  If a job is not accepted, the corresponding `SourcedJobPostingPayload` will have a null aggregationId, with an error in the standard GraphQL errors object.
  """
  createSourcedJobPostings(input: CreateSourcedJobPostingsInput): CreateSourcedJobPostingsPayload

  """
  Expire a posted job using the `sourcedPostingId` that was returned when the job was submitted.

  Indeed will not verify if the job exists before sending back a response, so the response will always be ACCEPTED.

  """
  expireSourcedJobsBySourcedPostingId(input: ExpireSourcedJobsBySourcedPostingIdInput!): ExpireSourcedJobsBySourcedPostingIdPayload

  """

}

####################
# BEGIN RETURN TYPES
####################

type CreateSourcedJobPostingsPayload {
  """
  A payload for each posting in the SourcedJobPostings mutation.

  The values cannot be null. However, the corresponding `jobPosting` object may be null inside each value. This indicates that the job was not accepted by Indeed.
  Refer to the returned Error object for details on anything not accepted.
  """
  results: [CreateSourcedJobPostingResult!]!
}

"""
An intermediary type in which a null `jobPosting` indicates there was a problem creating the job. The GraphQL errors object indicates the problem for each null value by indicating the index in the error object.

This object may be updated with additional fields later that would be associated with each posting in a request, regardless of whether it was successful.
"""
type CreateSourcedJobPostingResult {
  jobPosting: SourcedJobPosting
}

"""
Result for successfully accepted job postings. Accepted means that Indeed considers this
a complete job, accepted for indexing.

Even if accepted, the job must adhere to Indeed's [Job Posting Standards](https://indeed.force.com/employerSupport1/s/article/115005915763), or it may not show up on Indeed.

See [My job is not shown on Indeed](https://indeed.force.com/employerSupport1/s/article/115005915763) if accepted
jobs do not appear on Indeed.
"""
type SourcedJobPosting {
  """
  Indeed Employer Job ID - Indeed generates this unique identifier for each job. We recommend storing this ID as it will be useful for other Indeed APIs.

  """
  sourcedPostingId: ID!
}

##################
# END RETURN TYPES
##################

"""
Base input for the SourcedJobPosting mutation, supporting bulk aggregation
of multiple job postings.
"""
input CreateSourcedJobPostingsInput {
  """
  Create the job posting.
  """
  jobPostings: [CreateSourcedJobPostingInput!]!
}

"""
This is the object in which all of the job data is contained.
"""
input CreateSourcedJobPostingInput {
  """
  Description of the job itself
  """
  body: SourcedJobPostingBodyInput!
  """
  Information about the job posting
  """
  metadata: SourcedJobPostingMetadataInput!
  """
  How to apply
  """
  applyMethod: SourcedJobPostingApplyMethodInput
}

"""

RichFormatting - [Follow these guidelines](https://developer.indeed.com/docs/indeed-apply/direct-employer/#formatting_guidelines)
"""
enum SourcedJobPostingFormattingType {
  RICH_FORMATTING,
  TEXT
}


"""
Structured salary

Use these fields if you are able to provide structured salary data.
currency accepts ISO 4217 codes (ex: USD, JPY)
salaryFrequency accepts hour, day, week, month, mile, or year
Use salaryMinimum and salaryMaximum to express a salary range.
Use the minor currency units for your locale. So, in the US, USD's minor currency unit is cents. In Japan, Yen is the most minor unit.
If your salary is fixed, set both fields to the same value.
If your salary has only a minimum, only set the minimum field.
"""
input SourcedJobPostingSalaryInput {
  currency: CurrencyCode!
  "Maximum amount in LOCAL MINOR CURRENCY"
  maximumMinor: Int64
  "Minimum amount in LOCAL MINOR CURRENCY"
  minimumMinor: Int64!
  """
  Period to pay

  To ensure your job shows on Indeed, one of the following values is recommended.
  * `DAY`
  * `HOUR`
  * `MILE`
  * `MONTH`
  * `WEEK`
  * `YEAR`
  """
  period: String! @OneOfFieldConstraint(values: ["DAY", "HOUR", "MILE", "MONTH", "WEEK", "YEAR"])
  """
  Optional extra information related to the job's salary
  """
  fineGrainedSalaryInformation: SourcedJobPostingFineGrainedJobSalaryInput
}

"""
This section includes salary input fields.

Salary values should use minor currency units (i.e. cents, pence, yen).
"""
input SourcedJobPostingFineGrainedJobSalaryInput {
  """
  The period that applies to other fields in this input object.

  For example, if `salaryPeriod == MONTHLY`, then working hours below would
  mean the expected number of working hours per month.

  To ensure your job shows on Indeed, one of the following values is recommended.
  * `DAY`
  * `HOUR`
  * `MILE`
  * `MONTH`
  * `WEEK`
  * `YEAR`
  """
  salaryPeriod: String! @OneOfFieldConstraint(values: ["DAY", "HOUR", "MILE", "MONTH", "WEEK", "YEAR"])

  """
  The total salary of a job for the specified period, in minor currency units.

  Examples:
  $102.50 -> 10250102.50 -> 10250
  £102.50 -> 10250
  ¥10250  -> 10250
  """
  totalSalaryMinor: Int64

  """
  Expected number of hours of work expected per period. This is not necessarily a maximum number of hours
  a worker may work during the `salaryPeriod`, but an expected minimum.
  """
  workingHours: Float

  """
  Total maximum overtime work hours for this job in the specified period (via `salaryPeriod`)
  """
  totalOvertimeHours: Float

  """
  Statutory overtime work hours in the specified period (via `salaryPeriod`)

  Accepts fractions of an hour, with this being a `Float` type.
  """
  statutoryOvertimeHours: Float

  """
  If the over time pay is not included in the pay (see `fixedOvertimePay`), and is a fixed
  value, specify it here.

  Examples:
  $102.50 -> 10250102.50 -> 10250
  £102.50 -> 10250
  ¥10250  -> 10250
  """
  fixedOvertimeSalaryMinor: Int64

  """
  If a base salary already presumes a certain number of overtime hours without pay, a
  job must positively affirm this here.

  Ex: In the US, jobs that meet certain requirements may be designated as "overtime exempt".
  For more information, see the US Department of Labor website: https://www.dol.gov/agencies/whd/fact-sheets/17a-overtime

  Ex: In Japan, fixed overtime means overtime wage is already fixed into the salary.
  This page is a good explainer: https://www.nsasia.co.jp/jsj/jobseeker/knowledge/what-is-fixed-overtime-pay
  """
  fixedOvertimePay: Boolean! = false
}

input SourcedJobPostingBodyInput {
  """
  PendingJob.title
  Job Data title

  The title of the job. Do not include other types of information, such as employment type,
  job location, job descriptions, copies, or headlines.
  """
  title: String! @Size(max : 255)

  """
  An optional subheader for the job title.

  Ex: "A bright and pleasant workplace"
  """
  subtitle: String @Size(max : 256)

  """
  PendingJob.description
  Job Data description

  The description for this job listing. Ensure the information in this field matches
  the details on the URL in your `SourcedJobPostingMetadataInput.url` field. This field will be the source of the raw text displayed to
  job seekers on Indeed, and should include all job-relevant information - including text that
  may also be provided in other fields in the XML, e.g., education, experience.

  Note: Job descriptions require HTML formatting. For a list of supported HTML elements, see
  [Formatting Guidelines](https://developer.indeed.com/docs/indeed-apply/enterprise-ats/#formatting_guidelines).
  """
  description: String! @Size(max : 65000)

  """
  How to parse description.
  """
  descriptionFormatting: SourcedJobPostingFormattingType! = TEXT

  """
  PendingJob.salary
  Metadata salary

  Structured salary information for this job
  """
  salary: SourcedJobPostingSalaryInput

  """
  In Japan, some jobs have probationary periods with different salaries. Only use this entity for jobs in Japan with a probationary period. If your job is not in Japan or is in Japan and does not have a probationary period, do not include this entity; it may lead to your job being moderated.
  """
  probationaryPeriod: SourcedJobPostingProbationaryPeriodInput

  """
  Job location
  """
  location: SourcedJobPostingLocationInput!

  """
  A list of benefits associated with the jobs.

  Separate distinct benefits should be multiple strings in the list.

  If the job does not have explicit non-salary benefits, provide an
  empty list.
  """
  benefits: [String!]!
}

input SourcedJobPostingProbationaryPeriodInput {
  """
  Text description of the probationary period.
  """
  probationaryConditions: String

  """
  Salary information for the probationary period
  """
  probationarySalary: SourcedJobPostingSalaryInput
}

"""
Implementation detail: We'll need to format this info into the existing detailed_location UDDT.
"""
input SourcedJobPostingLocationInput {
  """
  Latitude and longitude of the location where the job will be performed.
  """
  latitude: Float
  longitude: Float

  """
  PendingJob.location
  PendingJob.locationHint
  Job Data

  The country in which this job is located.
  Corresponds to GIS admin0
  """
  country: CountryCode!


  """
  The street address of the job's primary work location. Please include the street name and
  number.
  Ex: 1600 Pennsylvania Ave
  Ex: Mori Tower, 3F, Mouri 1-chome
  """
  streetAddress: String

  """
  Provide the city, admin regions for your country (e.g. state, county, prefecture), and postal code.
  Ex: Washington, District of Columbia, 20500
  Ex: Koto-ku, Tokyo, 100-8666
  Ex: Mayfair, London, W1J

  """
  cityRegionPostal: String! @NotBlank
}

input SourcedJobPostingMetadataInput {
  """
  Information about the source of the job, whether a company, agency, franchise, etc.
  """
  jobSource: SourcedJobPostingJobSourceInput!

  """
  jobPostingId should be a unique identifier for the job within the ATS. This was historically referred to as the “reference number”.
  """
  jobPostingId: String!

  """
  Use jobRequisitionId if you have a requisition ID in your ATS that is not necessarily unique.
  """
  jobRequisitionId: String

  """
  Use evergreenRequisitionId when there are multiple job postings over time for the same job.
  """
  evergreenRequisitionId: String

  """
  Use talentPoolId to track the talent pool applications to this job should be tagged to.
  """
  talentPoolId: String

  """
  Information on how to classify the job in Indeed's systems
  """
  taxonomyClassification: SourcedJobPostingTaxonomyInput

  """
  The date on which this job was first published.
  Note: If the publish date is posted on your site, the date on the site must match here.
  """
  datePublished: DateTime!

  """


  The URL for this job listing on your site. Use the URL for the job description page, not the
  application page. Include the source=Indeed token URL parameter to track clicks from Indeed.
  """
  url: WebUrl!

  """
  Number of people that will be hired for this job posting.
  """
  numberOfHires: Int

  """
  The date on which you will no longer be actively hiring for this posting.

  If provided, and not changed by a future request, the job may be removed from Indeed.

  """
  expirationDate: DateTime
  """
  The photos that should be associated with the job.
  Deprecated, use the 'photos' field.
  """
  photoUrl: [WebUrl!] #@deprecated(reason: "Use 'photos'")

  """
  Photo information for the job
  """
  photos: [SourcedJobPostingPhotoInput!]

  """
  User and contact information for the job.
  """
  contacts: [SourcedJobPostingJobContactInput!]
}

input SourcedJobPostingPhotoInput {
  """
  URL of the photo.
  """
  photoUrl: WebUrl!
}

"""
Listing job contacts provides these users access to the job in some systems on Indeed.
"""
input SourcedJobPostingJobContactInput {

  """
  In contactType, list the closest roles the contact has:
  For the primary contact information for the job, including where applications should be sent:
  'contact'
  For the hiring manager:
  'hiring manager'
  For a recruiter:
  'recruiter'
  For other users, such as interviewers or coordinators:
  'user'

  If a job's contact is also the recruiter, for instance, include both 'contact' and 'recruiter' here.
  """
  contactType: [String!]! @OneOfFieldConstraint(values: ["contact", "hiring manager", "recruiter", "user"])

  """
  Information on how to reach the contact
  """
  contactInfo: SourcedJobPostingJobContactInfoInput!
}

input SourcedJobPostingJobContactInfoInput {
  """
  Email address of the contact for the job.

  This is required by our Search Quality team.
  """
  contactEmail: EmailAddress!

  """
  Optional name for the client's contact
  """
  contactName: String

  """
  Optional phone number for the client's contact
  """
  contactPhone: PhoneNumber
}


input SourcedJobPostingJobSourceInput {
  """
  The name of the company where the job will be performed.
  """
  companyName: String! @NotBlank

  """
  The root website of the company, for example, "https://www.indeed.com". If the company is a franchise, use the brand website.
  """
  companyWebsite: WebUrl

  """
  The name of the parent organization that is hiring for the role. For example, if there are subsidiaries or franchises with multiple branded locations under the same company, all of those jobs should still have the same value in this `sourceName` field.
  # When a user claims their jobs on Indeed this identifier will determine the ganular group of jobs that are associated with their account.
  """
  sourceName: String! @Size(max : 250)

  """
  Use this field to indicate the type of organization that is hiring for this role.

  If the source of this job is the direct employer, an ad agency acting on behalf of the direct employer, or a recruitment process outsourcer (RPO), use "Employer" in this field.
  If the source of this job is a job board, use “Job Board” in this field.
  If the source of this job is a staffing agency or recruitment firm, use “Staffing Agency” in this field.
  """
  sourceType: String! @OneOfFieldConstraint(values: ["Employer", "Job Board", "Staffing Agency"])

  """
  externalEmployerId is used internally by Indeed. Only use this field if directed.
  """
  externalEmployerId: ID

  """
  Include advertiserId if you know the Indeed advertiserId you plan to use to sponsor this job.
  """
  employerKey: ID
}


input SourcedJobPostingTaxonomyInput {
  """
  The type(s) of job, full- or part-time, contract, etc.
  """
  jobTypes: [String!]

  """
  A list of job categories, used to aid job seekers when searching. While this element is
  not required, it’s highly encouraged to include for job management or as a way to reflect
  additional information found on the job details page. For example, you may find it useful
  to include facility or department information in this element.
  """
  categories: [String!]

  """

  Use one of the following strings to best describe this role's location flexibility.

  “Fully Remote”
  Use this text to indicate the job can be performed remotely. No on-site work is required.
  “Hybrid Remote”
  Use this text to indicate the job involves a mix of on-site and remote days, typically within the same week.
  “COVID-19”
  Use this text to indicate that the job is fully remote for COVID-19 related circumstances, but will eventually return to being partially or fully office-based.

  Note: The data collected from this field will not be used to determine location.
  """
  remoteType: String @Size(max : 1000)

  """

  The desired education level for this job.
  """
  education: String

  """
  PendingJob.experience
  Metadata experience

  The desired experience for this job.
  """
  experience: String

  """
  Occupation(s) applicable to this job.
  """
  occupations: [String!]

  """
  # SUID(s) or string(s) representing taxonomy attributes.

  See https://docs.google.com/document/d/16j9W_6sZHx4PgT2q_Rux4I2Mim6iXinFXvnlDKK212E/edit#bookmark=id.9giq2ldjg05y
  """
  attributes: [String!]

  """
  Raw taxonomy input, for rare cases. Using this improperly may result in the job not appearing on Indeed.

  See https://docs.google.com/document/d/16j9W_6sZHx4PgT2q_Rux4I2Mim6iXinFXvnlDKK212E/edit#bookmark=id.9giq2ldjg05y
  """
  attributesBlob: String
}

"""
As additional input methods are defined (e.g., third party email or url) additional fields will be added for these new types.
"""
input SourcedJobPostingApplyMethodInput {
  """
  Apply via Indeed's Indeed Apply mechanism
  """
  indeedApply: SourcedJobPostingIndeedApplyInput
}

"""
The following indeed-apply parameters are gathered from other parts of the schema:
indeed-apply-jobUrl - (JobMetadataInput.postUrl) The canonical URL to the complete job description.
indeed-apply-jobId - (JobMetadataInput.jobPostingId) The ID of the job, used for your own internal tracking.
indeed-apply-jobCompanyName - (JobSourceInput.companyName) The name of the company. If you do not include a companyName, we derive this from other information in JobSourceInput.
indeed-apply-jobLocation - (PendingJob.location - repeated) The location of the job.
indeed-apply-jobTitle - (JobBodyInput.title) The title of the job to display externally.
"""
input SourcedJobPostingIndeedApplyInput {
  """
  Any arbitrary information you want to provide.
  This information is not displayed externally, but it will be sent when using
  the apply via post URL.
  """
  jobMeta: String

  """
  The URL to which Indeed will post the application data. Encode this URL. Must be HTTPS.
  """
  postUrl: WebUrl! @NotBlank

  """
  A string value indicating whether the phone number field should be displayed on the job.

  The default value is optional.
  """
  phoneRequired: JobRequirement = OPTIONAL

  """
  A string value indicating if the message or cover letter field is required.
  The default value is optional.
  """
  coverLetterRequired: JobRequirement = OPTIONAL

  """
  Specifies whether to require a resume, make a resume optional, or to hide the resume upload option from the user.

  The default value is required. If you select optional or hidden, you must include screener questions.  You must support .pdf, .doc, .docx, .rtf, and .txt formats for resumes.
  """
  resumeRequired: JobRequirement = YES

  """
  List of resume fields that must be included in the jobseeker resume.
  This field overwrites the phone configuration parameters above if provided.
  """
  resumeFieldsRequired: [IndeedApplyConfigurationResumeField!]

  """
  List of resume fields that should preferably be in the jobseeker resume.
  This field overwrites the phone configuration parameters above if provided.
  """
  resumeFieldsOptional: [IndeedApplyConfigurationResumeField!]

  """
  Indicates if the name field in the apply form should be split into first and last name, or if a single field for the full name is sufficient.
  """
  nameFormat: IndeedApplyNameFormatType

  """
  A URL which returns a JSON-formatted string of questions to be asked during the Indeed Apply application process.
  """
  applyQuestions: WebUrl

  """
  Required for Indeed apply.  See for more details:
  https://developer.indeed.com/docs/indeed-apply/enterprise-ats/#generating_an_api_token
  """
  apiToken: ID! @NotBlank
}

enum JobRequirement {
  NO,  # Replaces `Hidden`
  OPTIONAL,
  YES  # Replaces `Required`
}

enum IndeedApplyNameFormatType {
  """
  Show two fields for an applicant's first and last name
  """
  FIRST_LAST_NAME,
  """
  Show a single field for an applicant's full name
  """
  FULL_NAME
}

"""
Values for setting required and optional resume fields.

See the following for
[IA Configuration Resume Fields for XML](https://developer.indeed.com/docs/indeed-apply/enterprise-ats/#ia_configuration_resume_fields).
This API has the same support.
"""
enum IndeedApplyConfigurationResumeField {
  ###########################
  # Supported by all markets.
  ###########################
  PHONE,
  LOCATION,
  POSITIONS,
  EDUCATION,
  SKILLS,
  LANGUAGES,
  CERTIFICATIONS,

  ##########################################################################
  # Supported by the JP market only.
  #
  # These will have no impact on jobs outside of Japan. There is no need
  # to worry about this creating legal risk, for example, if a job in the US
  # tried to require gender.
  ##########################################################################
  NAME_PRONUNCIATION,
  DATE_OF_BIRTH,
  GENDER,
}

# Type and input for expiration endpoints

type ExpireSourcedJobsBySourcedPostingIdPayload {
  """
  The payload for expireSourcedJobsByPostingId mutation.
  """
  results: [ExpireSourcedJobResult!]!
}


type ExpireSourcedJobsByJobPostingIdAndSourcePayload {
  """
  The payload for expireSourcedJobsByJobPostingIdAndSource mutation.
  """
  results: [ExpireSourcedJobResult!]!
}

type ExpireSourcedJobResult {
  """
  The value of trackingKey returned would be the internal tracking Id from logrepo
  """
  trackingKey: String!

}


"""
Base input for the expireSourcedJobsByPostingId mutation, supporting bulk expiration
of multiple jobs.
"""
input ExpireSourcedJobsBySourcedPostingIdInput {
  """
  The jobs to be expired by postingId.
  """
  jobs: [ExpireSourcedJobBySourcedPostingIdInput!]!
}

"""
Base input for the expireSourcedJobByJobPostingIdAndSourceInput mutation, supporting bulk expiration
of multiple jobs.
"""
input ExpireSourcedJobsByJobPostingIdAndSourceInput {
  """
  The jobs to be expired by jobPostingId and sourceName.
  """
  jobs: [ExpireSourcedJobByJobPostingIdAndSourceInput!]!
}

"""
This schema is supporting both a single jobId (EJID generated by job creation) and the combination of jobPostingId (referenceId) and source name.
Eventually we might only use the single jobId as the input.
"""
input ExpireSourcedJobBySourcedPostingIdInput {
  """
  Currently targeting an "Employer Job ID" from EJMS. This is a UUID4 value. The ATS can get it from the response of job creation.
  """
  sourcedPostingId: ID! @NotBlank
}

"""
The input that contains jobPostingId (referenceId) plus source name.
"""
input ExpireSourcedJobByJobPostingIdAndSourceInput {
  """
  jobPostingId should be a unique identifier for the job within the ATS.  This ID can be used by the client with their client ID and sourceName to expire the job. This was historically referred to as the “reference number”.
  PendingJob.referenceId
  Job Data referenceId
  """
  jobPostingId: ID! @NotBlank
  """
  The parent organization that is hiring for the role. For example, if there are subsidiaries or franchises with multiple branded locations under the same company, all of those jobs should still have the same value in this `sourceName` field.
  """
  sourceName: String! @Size(min: 1, max : 250)
}

directive @NotBlank on INPUT_FIELD_DEFINITION
directive @Size(min : Int = 0, max : Int) on INPUT_FIELD_DEFINITION
directive @OneOfFieldConstraint(values : [String!]!) on INPUT_FIELD_DEFINITION


Back to top