- API overview
- API steps
- 1. Authenticate
- 2. Update job posting
- 3. Choose the query method to get job posting information
- 4. Get job posting status by ID
- 5. List job postings by criteria
- Request – List job postings by criteria
- Response – List job postings by criteria
- 6. Clear job posting updates
- 7. Troubleshoot errors
- Job status
- Organic non-sponsored jobs
- Sponsored jobs
- Rejected jobs
- Rate limits
- FAQs
- GraphQL API references
Job Update API guide
Update, clear updates, get details for, and list job postings on Indeed.
By using this API and its documentation and building an integration, you agree to the Additional API Terms and Guidelines.
API overview
Use the Job Update API to update jobs, clear updates, get job details, and list jobs on Indeed. These calls are free and do not count toward Sponsored Jobs API usage policy limits.
The ATS controls job creation and expiration.
API steps
1. Authenticate
When you become an Indeed partner, Indeed creates an app for your integration. Sign in to Partner Console (opens in a new window) to view the app and your OAuth credentials: client ID, secret, and a 3-legged OAuth authorization code. Exchange those credentials for an access token to authenticate API calls.
The findEmployerJobsPartner, node, and nodes queries require one of these OAuth token types:
| Token type | Description |
|---|---|
| 2‑legged OAuth token that specifies an advertiser | If you already use the Sponsored Jobs API with the client credentials grant type (2-legged OAuth), you might already have this token type. |
| 3-legged OAuth token | If you already have a 3-legged OAuth token, you can use it. |
The access token must include the employer_access and employer.hosted_job scopes. For more information, see Scopes.
After you get an access token, include it in the query.
Indeed recommends refreshing access tokens before they expire, so users do not need to sign in each time they view updated job statuses.
2. Update job posting
This beta feature is not available in Japan. Contact Indeed for more information.
If you use delayed publishing, a successful update also publishes a held job.
- If you submitted the job posting to Indeed, upsert it.
- If another partner submitted it, update the posting. Updates are primarily for ad agencies.
Japan only: All partners except ad agencies and Indeed PLUS Publisher Network partners can update the posting.
See also:
Enables ad agencies to update job posting fields on Indeed and Indeed PLUS.
Update only jobs authorized by your client. Unauthorized updates can prevent the client from using other Indeed tools on that job. To add a location the ATS does not have, use your XML or API integration.
To update a job posting that you did not send to Indeed:
-
Choose clients to add: Contact Indeed.
-
Call
findEmployerJobsPartnerand look for jobs whereJobPostSurfaceStatus.isRejectedisfalse. Those jobs are searchable on Indeed. SaveEmployerJob.idfor the next step. If a job is rejected, checkstatusCommunicationfor the reason. -
Call
updateSourcedJobPostingswith the savedEmployerJob.idassourcedPostingId. You can update fields such as the tracking URL and job URL. For the full list, seeUpdateSourcedJobPostingMetadataInputandUpdateSourcedJobPostingBodyInput. After you update a field, later ATS changes to that field no longer appear to job seekers. -
Confirm your updates
Use one of these queries:
findEmployerJobsPartner: Call at most once per hour per access token. Updates usually appear within 30 seconds, but Indeed does not guarantee timing.node: Fetch one job byEmployerJob.id.nodes: Fetch multiple jobs byEmployerJob.idvalues in one call.
To list sponsored jobs for a campaign, set
campaignCategoriestoad_campaign_category:{category}. For example,ad_campaign_category:fooreturns sponsored jobs for thefoocampaign.
For rate limits on this call, see Rate limits.
Request – Update job posting
To update selected fields in a job posting, call the jobsIngest.updateSourcedJobPostings mutation.
This operation requires an access token that represents the advertiser, with the employer_access and employer.hosted_job scopes. See Get access token that represents employer, and associate the token with an advertiser ID rather than an employer ID.
Provide only the fields that you want to update. To leave a field unchanged, omit it or set it to null.
This example calls the updateSourcedJobPostings mutation to update several fields in a job posting:
mutation UpdateSourcedJobPostings { jobsIngest { updateSourcedJobPostings( input: { updates: [ { sourcedPostingId: "<SOURCED POSTING ID OF JOB POSTING>" metadata: { url: "https://www.example.com/jobs/123" campaignCategories: ["springCampaign"] trackingUrl: "https://www.example.com/jobs/123?utm_source=indeed" } body: { title: "Software Developer" description: "Come build the future with us!" jobLocation: { general: { cityRegionPostal: "Phoenix, AZ 85003" streetAddress: "1234 Sunny Lane, Phoenix, AZ 85003" } } salary: { currency: "USD" maximumMinor: 1000 minimumMinor: 1000 period: "HOUR" } } } ] } ) { results { jobPosting { sourcedPostingId employerJobId } } } }}mutation UpdateSourcedJobPostings { jobsIngest { updateSourcedJobPostings(input: { updates: [{ sourcedPostingId: "<SOURCED POSTING ID OF JOB POSTING>" body: { description: "<h2>About the role</h2><p>Join our team as a software engineer. You design and build scalable backend services.</p><ul><li>Competitive salary</li><li>Remote-friendly</li><li>Health and dental benefits</li></ul>" descriptionFormatting: HTML } }] }) { results { jobPosting { sourcedPostingId employerJobId } } } }}updateSourcedJobPostings takes one argument: input, of type UpdateSourcedJobPostingsInput.
input has one field, updates, which is an array of UpdateSourcedJobPostingInput objects. Each object defines updates for one job posting.
Each UpdateSourcedJobPostingInput object supports these fields:
| Field | Type | Description |
|---|---|---|
sourcedPostingId | ID! | Required. For each job posting, provide one of these values:
|
metadata.url | WebUrl | The ad agency URL where a job seeker can apply if Indeed Apply is not available. |
metadata.campaignCategories | [String!] | Category tags that group related jobs for campaign selection. Query Sponsored Jobs API with ad_campaign_category:{category} for the values you provide. |
metadata.trackingUrl | WebUrl | The job tracking URL. When a job seeker views the job on Indeed, Indeed sends an HTTP request to this URL. |
body.title | String | The job title. |
body.description | String | The job description:
|
body.descriptionFormatting | DescriptionFormatting | The job description format. Set this field to HTML to enable HTML formatting. The default is TEXT. |
body.jobLocation .general.cityRegionPostal | String! | Required if you update location. The city, administrative region such as state, county, or prefecture, and postal code for the job. If you set this field for a remote job, Indeed treats the job as a regional remote job. |
body.jobLocation .general.streetAddress | String | The street address of the job's primary location. Include the full address so Indeed can improve location matching and show the address to job seekers. |
body.salary.currency | CurrencyCode | The salary currency code in ISO 4217 format. |
body.salary.maximumMinor | Int64 | The maximum salary in local minor currency. For USD, 1000 means $10. To clear the current value, omit this field or set it to null. |
body.salary.minimumMinor | Int64 | The minimum salary in local minor currency. For USD, 1000 means $10. To clear the current value, omit this field or set it to null. |
body.salary.period | JobSalaryPeriod! | Required if you update salary. The rate used to calculate salary, such as hourly, daily, or weekly. |
body.companyName | String | Updates the company name for the job posting. To leave it unchanged, omit this field or set it to null. Maximum length: 200 characters. |
For more information, see the API reference.
Confirm your updates
The updateSourcedJobPostings response includes job IRIs. To verify changes, use the node query with the returned IRIs.
See Get job posting status by ID.
Partial updates
Field updates are not atomic. If you update multiple fields in one request, the API can rarely apply only some changes. When that happens, the response includes an error.
An error response does not always mean a partial update occurred. If a multi-field request returns an error, view the updated job posting to confirm which fields, if any, succeeded. For error-handling guidance, see Troubleshoot GraphQL errors.
Response – Update job posting
updateSourcedJobPostings returns an UpdateSourcedJobPostingsPayload object. It includes a results field, which is an array of UpdateSourcedJobPostingResult objects. Each object corresponds to one job posting update.
Each UpdateSourcedJobPostingResult object includes a jobPosting field of type SourcedJobPostingUpdate. If Indeed does not accept the update, this field is null. Otherwise, it includes:
sourcedPostingId: The job posting UUID. This matchesSourcedJobPosting.sourcedPostingId, which Indeed generated when it created the job posting. Use this value to expire or update the job posting.employerJobId: The Indeed Resource Identifier (IRI) for the job posting. This matchesEmployerJob.id.
3. Choose the query method to get job posting information
Use one of these methods to get job information:
-
Get job posting status by ID (
nodeornodesquery)Use this method when you already have the job IRI, which is the
EmployerJobID. It is faster thanfindEmployerJobsPartnerwhen you already know the IRI.Use cases:
- Confirm updates: Call
updateSourcedJobPostingsto get the job IRI, then usenodeto verify the change. - Webhook handling: After you receive a webhook, use
nodewith the job IRI from the payload to get the current job state.
- Confirm updates: Call
-
List job postings by criteria (
findEmployerJobsPartner)Use this search-based method to list all jobs. It works best for bulk inventory retrieval.
Use case:
- Inventory sync: When you do not have IRIs, call
findEmployerJobsPartnerto retrieve associated jobs in bulk. OnlylegacySourceIdandfeedTypefilters are available.
- Inventory sync: When you do not have IRIs, call
4. Get job posting status by ID
To get job status by IRI, which is the EmployerJob ID, call the GraphQL node or nodes query. When you already have the IRI, these queries are faster than List job postings by criteria. Job IRIs are base64-encoded.
Authentication
Use the same authentication and authorization as findEmployerJobsPartner. See Authenticate.
Single job query (node)
Get one job by its IRI with the Relay Node pattern:
query GetJob($id: ID!) { node(id: $id) { ...on EmployerJob { id jobData { title description company dateCreated datePostedOnIndeed jobLocation { city countryCode fullAddress } salary { max min period } externalPostingMetadata { jobPostingId jobRequisitionId } } managementUrls { viewJob } } }}Variables:
{ "id": "dXJuOmluZGVlZDplbXBsb3llcmpvYjphMWIyYzNkNC1lNWY2LTc4OTAtYWJjZC1lZjEyMzQ1Njc4OTA="}The id field in the response is the encoded IRI. See EmployerJob.
POST https://apis.indeed.com/graphqlAuthorization: Bearer YOUR_ACCESS_TOKENContent-Type: application/json
{ "query": "query GetJob($id: ID!) { node(id: $id) { ... on EmployerJob { id jobData { title description company } } } }", "variables": { "id": "dXJuOmluZGVlZDplbXBsb3llcmpvYjphMWIyYzNkNC1lNWY2LTc4OTAtYWJjZC1lZjEyMzQ1Njc4OTA=" }}Batch job query (nodes)
Get status for multiple jobs by their IRIs. Use this query to confirm several updates at once or to process batches of webhook events.
query GetMultipleJobs($ids: [ID!]!) { nodes(ids: $ids) { ... on EmployerJob { id jobData { title description company datePostedOnIndeed jobLocation { city countryCode } } } }}Variables:
{ "ids": [ "dXJuOmluZGVlZDplbXBsb3llcmpvYjphMWIyYzNkNC1lNWY2LTc4OTAtYWJjZC1lZjEyMzQ1Njc4OTA=", "dXJuOmluZGVlZDplbXBsb3llcmpvYjpiMmMzZDRlNS1mNmE3LTg5MDEtYmNkZS1mMjM0NTY3ODkwMTI=", "dXJuOmluZGVlZDplbXBsb3llcmpvYjpjM2Q0ZTVmNi1hN2I4LTkwMTItY2RlZi0zNDU2Nzg5MDEyMzQ=" ]}The id fields in the response are the encoded IRIs. See EmployerJob.
Work with webhooks
Webhook payloads include the job IRI:
{ "eventType": "job.updated", "jobIri": "dXJuOmluZGVlZDplbXBsb3llcmpvYjphMWIyYzNkNC1lNWY2LTc4OTAtYWJjZC1lZjEyMzQ1Njc4OTA=", "timestamp": "2025-12-10T15:30:00Z"}Use the node query with the IRI to get the current job status. See Single job query (node).
5. List job postings by criteria
Contact Indeed to use this feature. Additional requirements apply.
Lists job postings for an employer.
If you call this operation once per hour for each client, you are unlikely to hit the rate limit. If you do receive HTTP 429, reduce your request rate. For rate limits on this call, see Rate limits.
Request – List job postings by criteria
To list an employer's jobs, call findEmployerJobsPartner:
This example calls findEmployerJobsPartner to list job postings in descending order by the Indeed post date.
query FindEmployerJobsPartner { findEmployerJobsPartner(input: { filters: { legacySourceId: "60a9614a5d973a21", jobFeedType: ["INTEGRATED_FROM_PARTNER"] }, sort: [{ sortDirection: DESC, sortField: datePostedOnIndeed }] }, first: 10, before: null, after: null) { employerJobs { id jobData { title datePostedOnIndeed dateCreated description company jobLocation { countryCode city postalCode fullAddress } externalJobPageUrl externalPostingMetadata { jobPostingId jobRequisitionId campaignCategories trackingUrls rawInputLocation isIntegratedJob } } managementUrls { viewJob } seatsConnection { pageInfo { endCursor hasNextPage hasPreviousPage startCursor } seats { jobPost { status(input: { # Required input for messages to appear surfaceContexts: [{ statusCommunicationSettings: { communicationLocale: "en" } }] }) { surfaceStatuses { isRejected statusCommunication { messagingTagMatches { # Returns a rejection reason if a message is found. message } } } } } } } } }}findEmployerJobsPartner finds jobs by source ID or lists an employer's jobs. It supports these input fields:
| Field | Description |
|---|---|
Filters jobs by feed type, which identifies where the job came from. Valid values are:
Specify one value, unless you pair Default: By default, | |
Filters jobs by the ATS requisition ID on the job ( Send the value exactly as that field returns it. Matching is case-insensitive and does not trim whitespace. A fragment such as Requisition IDs are not unique, so one filter can match more than one job. By default, | |
input.sort | An array of objects that defines how to sort job postings in the response. Each object contains these fields:
|
first | The number of job postings to return.
|
before | Returns items with a cursor value before this value. Use PageInfo in the response for pagination details. |
after | Returns items with a cursor value after this value. Use PageInfo in the response for pagination details. |
Response – List job postings by criteria
findEmployerJobsPartner lists jobs for the employer associated with the access token.
If you include filters or sorting in the request, the API applies them to the results.
This example returns one seat, so in pageInfo, startCursor and endCursor match and hasNextPage and hasPreviousPage are false.
{ "data": { "findEmployerJobsPartner": { "employerJobs": [{ "id": "aXJpOi8vYXBpcy5pbmRlZWQuY29tL0VtcGxveWVySm9iLzkxZGU0ZjVhLWE1MWYtNGQ1Ni1iOWI0LWNhMDQzZWVjNDAzMQ==", "jobData": { "title": "Certified Nursing Assistant CNA", "externalPostingMetadata": { "jobPostingId": "CBA-Anytown-Posting-Id", "jobRequisitionId": "CBA-Anytown-Req-Id" }, "managementUrls": { "viewJob": "https://www.example.com/" } }, "seatsConnection": { "pageInfo": { "endCursor": "abc123", "hasNextPage": false, "hasPreviousPage": false, "startCursor": "abc123" }, "seats": [{ "jobPost": { "status": { "surfaceStatuses": [{ "isRejected": true, "isSponsorshipRequired": false, "isMissingRequiredSponsorship": false, "statusCommunication": { "messagingTagMatches": [{ "message": "Your job post appears to violate our policy guidelines and is not eligible to be shown in our search results." }] } }] } } }] } }] } }}If your token does not return the jobs you expect, ask the user to contact their Indeed support representative through their Indeed employer account page. The representative can help connect jobs on Indeed to the advertiser or user account, if appropriate.
The API returns a FindEmployerJobsPartnerConnection object with these fields:
| Field | Type | Description |
|---|---|---|
employerJobs | [EmployerJob]! | An array of job objects. For field details, see Response - View job posting. |
estimatedTotalResultsCount | Int! | The estimated total number of jobs in the response. |
pageInfo | PageInfo! | Pagination information. |
To troubleshoot validation errors, see Troubleshoot GraphQL errors. For more information, see Validation (opens in a new window) in the GraphQL documentation.
6. Clear job posting updates
This beta feature is not available in Japan. Contact Indeed for more information.
As an ad agency, use this operation to clear updates that you made to a client's job postings. The job returns to the latest ATS data, so the client can manage it again with other Indeed tools. For example, clear your updates when you stop working with a client.
-
Add clients and update their job postings.
-
Call
findEmployerJobsPartnerto confirm the updated values. SetlegacySourceIdto thesourcedPostingIdthat the update operation returned. -
Call
clearSourcedJobPostingUpdatesto clear the updated fields. -
Call
findEmployerJobsPartneragain with the samelegacySourceIdto confirm that the fields now show the ATS data.
For rate limits on this call, see Rate limits.
Request – Clear job posting updates
To clear fields that updateSourcedJobPostings previously changed, call the jobsIngest.clearSourcedJobPostingUpdates mutation.
This operation requires an access token for the advertiser that originally made the updates, with the employer_access and employer.hosted_job scopes. See Get access token that represents employer, and associate the token with an advertiser ID rather than an employer ID.
This example calls the clearSourcedJobPostingUpdates mutation to clear updates from a job posting:
mutation ClearSourcedJobPostingUpdates { jobsIngest { clearSourcedJobPostingUpdates(input: { updates: [{ sourcedPostingId: "<SOURCED POSTING ID OF JOB POSTING>" }] }) { results { jobPosting { sourcedPostingId employerJobId } } } }}clearSourcedJobPostingUpdates takes one argument, input, of type ClearSourcedJobPostingUpdatesInput.
input has one field, updates, which is an array of ClearSourcedJobPostingUpdateInput objects. Each object identifies one job posting whose updates you want to clear.
To clear updates for multiple job postings in one request, include multiple ClearSourcedJobPostingUpdateInput objects.
Each ClearSourcedJobPostingUpdateInput object supports these fields:
| Field | Type | Description |
|---|---|---|
sourcedPostingId | ID! | Required. For each job posting, provide one of these values:
|
Response – Clear job posting updates
clearSourcedJobPostingUpdates returns a ClearSourcedJobPostingUpdatesPayload object. It includes a results field, which is an array of ClearSourcedJobPostingUpdateResult objects. Each object corresponds to one job posting whose updates were cleared.
Each ClearSourcedJobPostingUpdateResult object includes a jobPosting field of type SourcedJobPostingUpdate. If Indeed does not accept the request, this field is null. Otherwise, it includes:
sourcedPostingId: The job posting UUID. This matchesSourcedJobPosting.sourcedPostingId, which Indeed generated when it created the job posting. Use this value to expire or update the job posting.employerJobId: The Indeed Resource Identifier (IRI) for the job posting. This matchesEmployerJob.id.
7. Troubleshoot errors
- For errors specific to the Job Update API, see Troubleshoot errors.
- For OAuth errors that occur before you reach GraphQL, see Troubleshoot OAuth errors.
- For GraphQL errors, see Troubleshoot GraphQL errors.
Job status
Because feed policy has changed, most jobs you manage on Indeed no longer come from your feeds.
A job has one of these statuses:
- Organic:
isRejectedandisSponsorshipRequiredare bothfalse. See Organic non-sponsored jobs. - Sponsored only with spend:
isRejectedisfalse,isSponsorshipRequiredistrue, andisMissingRequiredSponsorshipisfalse. See Sponsored jobs. - Sponsored only without spend:
isRejected,isSponsorshipRequired, andisMissingRequiredSponsorshipare alltrue. See Sponsored jobs. - Nowhere:
isRejectedistrueandisSponsorshipRequiredisfalse. See Rejected jobs.
Organic non-sponsored jobs
To get organic traffic data for jobs that are not sponsored, use one of these approaches:
- Add the
trackingUrlfield: You receive an HTTP request for each job click, for both organic and sponsored traffic, with or without Indeed Apply. SeetrackingUrlinUpdateSourcedJobPostingMetadataInput. - Use custom job URLs: Replace the job URL with a career page that you own, or work with ATS partners to append your tracking metadata. Job seekers go to this URL only if the job does not use Indeed Apply. See
urlinUpdateSourcedJobPostingMetadataInput. - Collaborate with client ATS: Work with your clients and their ATS to gather organic performance data.
- Have clients sign in to Indeed Analytics: Clients can view complete organic performance for all jobs, sponsored or not.
Sponsored jobs
To get sponsored-job traffic data, call Sponsored Jobs API v8 or later to get employer job IDs that match the IDs returned by the Job Update API.
Rejected jobs
When isRejected is true and isSponsorshipRequired is false, the job is not searchable on Indeed. Retrieve the status message to find out why and how to fix it:
-
Call
findEmployerJobsPartnerto list job postings by criteria.This example calls
findEmployerJobsPartnerto list job postings in descending order by the Indeed post date.query FindEmployerJobsPartner {findEmployerJobsPartner(input: {filters: {legacySourceId: "60a9614a5d973a21",jobFeedType: ["INTEGRATED_FROM_PARTNER"]},sort: [{sortDirection: DESC,sortField: datePostedOnIndeed}]}, first: 10, before: null, after: null) {employerJobs {idjobData {titleexternalPostingMetadata {jobPostingIdjobRequisitionId}}seatsConnection {seats {jobPost {status(input: { # Required input for messages to appearsurfaceContexts: [{statusCommunicationSettings: {communicationLocale: "en"}}]}) {surfaceStatuses {isRejectedisSponsorshipRequiredisMissingRequiredSponsorshipstatusCommunication {messagingTagMatches {# Returns a rejection reason if a message is found.message}}}}}}}}}} -
In the response, find the jobs where
JobPostSurfaceStatus.isRejectedistrueandJobPostSurfaceStatus.isSponsorshipRequiredisfalse.Save
EmployerJob.idfor later API calls. -
Read the rejection reason in
JobPostStatusMessagingTagMatch.message, underJobPostSurfaceStatus.statusCommunication. The message describes the problem, not the rule that triggered it.{"data": {"findEmployerJobsPartner": {"employerJobs": [{"id": "aXJpOi8vYXBpcy5pbmRlZWQuY29tL0VtcGxveWVySm9iLzkxZGU0ZjVhLWE1MWYtNGQ1Ni1iOWI0LWNhMDQzZWVjNDAzMQ==","jobData": {"title": "Certified Nursing Assistant CNA","externalPostingMetadata": {"jobPostingId": "CBA-Anytown-Posting-Id","jobRequisitionId": "CBA-Anytown-Req-Id"}},"seatsConnection": {"seats": [{"jobPost": {"status": {"surfaceStatuses": [{"isRejected": true,"isSponsorshipRequired": false,"isMissingRequiredSponsorship": false,"statusCommunication": {"messagingTagMatches": [{"message": "Your job post appears to violate our policy guidelines and is not eligible to be shown in our search results."}]}}]}}}]}}]}}}
Rate limits
The Job Update API enforces rate limits on these operations:
findEmployerJobsPartnerto list job postings by criteriajobsIngest.updateSourcedJobPostingsto update job postingsjobsIngest.clearSourcedJobPostingUpdatesto clear job posting updates
Indeed sets these limits above normal daily Job Update API usage to keep the API available, but throttles large volumes over short periods, so spread requests over 10 minutes.
If you exceed a limit, the API returns HTTP 429, either in the HTTP response or in the GraphQL JSON errors array. Reduce your request rate, and if you need help working around these limits, request support (opens in a new window).
The limits are:
| Query or mutation | Rate limit |
|---|---|
findEmployerJobsPartner query | 5 requests per second |
jobsIngest.updateSourcedJobPostings mutation | 20 requests per second total across both mutations |
jobsIngest.clearSourcedJobPostingUpdates mutation |
See also:
- RFC 6585: Additional HTTP Status Codes - 429 Too Many Requests (opens in a new window)
- Rate limiting (Indeed PLUS)
FAQs
Job visibility
If a job uses Indeed Apply, EmployerJob.seatConnections.jobPost.isIndeedApplyActive returns true. Partners can retrieve this field with findEmployerJobsPartner.
GraphQL API references
clearSourcedJobPostingUpdates— Clears job updates.findEmployerJobsPartner— Lists jobs for an employer.node— Gets one job by ID.nodes— Gets multiple jobs by IDs.publishSourcedJobPostings.updateSourcedJobPostings— Updates supported job fields.