- Workflow
- Job Update API references
- Seats and job-level updates
- What updateSourcedJobPostings affects
- What clearSourcedJobPostingUpdates affects
- Seat management summary
- Authentication
- Add seats
- Add seats workflow
- Request — Add seats
- Response — Add seats
- Update seats
- Update seats workflow
- Request — Update seats
- Response — Update seats
- Clear seats
- Clear seats workflow
- Request — Clear seats
- Response — Clear seats
- Rate limits
- Troubleshoot errors
- FORBIDDEN: Advertiser is in a restricted moderation status
- FORBIDDEN: You are missing permissions for this action
- BAD_USER_INPUT: seatPostingId already exists
- UNAUTHENTICATED
- BAD_USER_INPUT
- NOT_FOUND
- DOWNSTREAM_SERVICE_ERROR or INTERNAL_SERVER_ERROR
Job Update API seat management guide
Add, update, or expire seats without changing the employer job.
このAPIとそのドキュメントを使用して連携を構築すると、APIに関する追加の利用規約およびガイドラインに同意したことになります。
An employer job is the employer’s hiring need, often called a requisition. A seat is one job listing for that employer job. One employer job can have multiple seats, such as the same role in different locations.
Use seat-based mutations to add, update, or expire seats without changing the employer job.
Workflow
- 1.Authenticate.
- 2.Add seats — Add a seat to an employer job and save the returned
jobKey. - 3.Update seats — Change seat-level location, salary, taxonomy, or URL.
- 4.Clear seats — Expire a seat from an employer job.
- 5.Review rate limits.
- 6.Troubleshoot errors — Resolve GraphQL errors.
Manage seats separately from the parent employer job. The ATS creates and expires the parent job. Seat operations affect only the seat, not the employer job.
Job Update API references
addSeats— Add a seat to an employer job.updateSeats— Update seat-level fields.clearSeats— Clear seat updates, or clear updates and expire an agency-created seat.findEmployerJobsPartner— List job postings for an employer.updateSourcedJobPostings— Update employer job-level fields that apply to all seats on a job.clearSourcedJobPostingUpdates— Clear job posting updates for ad agencies.
Seats and job-level updates
A job that uses seats can still use updateSourcedJobPostings and clearSourcedJobPostingUpdates, but those mutations update different layers. Understand this separation before you build a multi-seat integration.
What updateSourcedJobPostings affects
updateSourcedJobPostings updates the parent employer job, not individual seats. The fields it overrides, such as title, description, and company name, apply to the employer job and flow down to all seats.
If the employer job has multiple seats, updateSourcedJobPostings cannot update seat-level fields such as location, salary, campaign categories, or URL. Use updateSeats to update those fields on a specific seat.
For single-seat jobs, updateSourcedJobPostings can still update both employer job-level fields and seat-level fields in one call.
Fields without a seat-level equivalent, such as title, description, and company name, are always updated at the parent job level through updateSourcedJobPostings, even when a job has multiple seats.
What clearSourcedJobPostingUpdates affects
clearSourcedJobPostingUpdates is destructive for multi-seat jobs. It removes all parent job-level overrides from updateSourcedJobPostings, clears all seat-level updates from updateSeats, and expires all seats created with addSeats. Afterward, the parent job returns to the latest ATS data. ATS-created seats remain, but agency-created seats do not.
Do not use clearSourcedJobPostingUpdates to change one seat. Use clearSeats to expire or reset a single seat. If you want to revert seat-level field changes without expiring the seat, use updateSeats. You can also reactivate an expired agency-created seat with updateSeats, unless the parent employer job is expired.
Seat management summary
| Operation | Scope | Effect on seats |
|---|---|---|
updateSourcedJobPostings | Entire job for single-seat jobs, role-level job for multi-seat jobs | Returns an error if you update seat-level fields on a job with multiple seats. Use updateSeats instead. |
clearSourcedJobPostingUpdates | Entire job | Clears all job-level and seat-level updates and expires all agency-created seats. |
addSeats, updateSeats, clearSeats | Individual seat | Does not affect the parent job or other seats. |
Authentication
When you become an Indeed partner, Indeed sets up an app for your integration. Sign in to Partner Console to view your app and OAuth credentials (client ID, secret, and authorization code for 3-legged OAuth). Exchange credentials for an access token to authenticate API calls.
All seat mutations require a 2-legged access token for an advertiser ID.
The token must include these scopes:
employer_accessemployer.hosted_job
After you get the access token, include it in the request. Indeed recommends refreshing the token before it expires.
For more information about authentication, see Integrate with Indeed and call APIs, Get access token that represents employer, and Scopes.
Add seats
Call addSeats to add a seat to an employer job. Give each seat a unique seatPostingId. The response returns a jobKey. Save the jobKey for subsequent updateSeats and clearSeats calls.
addSeats fails if the seatPostingId already exists on the employer job. It also fails if the seat overlaps with another seat on the same job at the city level. Each job can have up to 10 seats.
Add seats workflow
- 1.
Identify the employer job. Use
createSourcedJobPostingsto get thesourcedPostingId(UUID). UsefindEmployerJobsPartnerto get theemployerJobId(base64-encoded IRI) or theEmployerJobtype. - 2.
Add a seat. Call
addSeatswith a uniqueseatPostingIdand the requiredbody.location. Each call adds one seat. - 3.
Save the
jobKey. The response returns thejobKeyfor the new seat. UseSeatIdentifierInput.jobKeyin laterupdateSeatsandclearSeatscalls. You can also get thejobKeyfromEmployerJobSeatMetaData. - 4.
Confirm the seat. Call
findEmployerJobsPartnerand inspectseatsConnectionto verify that the seat appears on the employer job.
For request details, see Request — Add seats, addSeats, and AddSeatsInput.
For employer job identifiers, see createSourcedJobPostings and findEmployerJobsPartner.
For response details, see Response — Add seats and AddSeatsPayload.
To verify the seat, see findEmployerJobsPartner and List job postings by criteria. For rate limits, see Rate limits.
Request — Add seats
Call jobsIngest.addSeats to add a seat to an employer job.
This operation requires an access token that represents the advertiser and includes these scopes:
employer_accessemployer.hosted_job
For more information about employer-scoped access tokens, see Get access token that represents employer. Associate the token with an advertiser ID, not an employer ID.
Example — GraphQL request to add a seat with location, salary, and metadata
mutation AddSeats { jobsIngest { addSeats(input: { seatAdditions: [{ employerJob: { sourcedPostingId: "<SOURCED POSTING ID>" } seatPostingId: "<YOUR UNIQUE SEAT ID>" body: { location: { country: "US" cityRegionPostal: "Austin, TX, 78701" streetAddress: "1234 Congress Ave" latitude: 30.2672 longitude: -97.7431 } salary: { currency: "USD" minimumMinor: 6000000 maximumMinor: 8000000 period: YEAR } } metadata: { taxonomyClassification: { remoteType: "Hybrid Remote" attributes: ["software-engineering", "full-time"] } campaignCategories: ["austinCampaign"] url: "https://www.example.com/jobs/austin-123" } }] }) { results { sourcedPostingId employerJobId seatPostingId jobKey } } }}addSeats takes one argument, input, of type AddSeatsInput. input includes one field, seatAdditions, which is an array of AddSeatInput objects. Each call adds one seat. Passing more than one item returns an error.
Each AddSeatInput object includes these fields:
| Field | Type | Description |
|---|---|---|
employerJob.sourcedPostingId | ID | Required. Provide exactly one of sourcedPostingId or employerJobId. UUID from createSourcedJobPostings. Identifies the employer job for the seat. |
employerJob.employerJobId | ID | Required. Provide exactly one of sourcedPostingId or employerJobId. Base64-encoded IRI from EmployerJob. Returned by createSourcedJobPostings or findEmployerJobsPartner. |
seatPostingId | ID! | Required. Agency-provided seat identifier. Must be unique across all seats on the employer job. Use it with the returned jobKey in later updateSeats and clearSeats calls. |
body.location.country | CountryCode! | Required. Country code. See Supported language codes, country codes, and locales. |
body.location.cityRegionPostal | String! | Required. City, region, and postal code. Examples:
|
body.location.streetAddress | String | Street address for the seat location. Example: |
body.location.latitude | Float | Latitude for the seat location. Use with longitude for precise coordinates. |
body.location.longitude | Float | Longitude for the seat location. |
body.salary.currency | CurrencyCode! | Required if you provide salary. ISO 4217 currency code, such as USD. |
body.salary.minimumMinor | Int64 | Minimum salary in the local minor currency. For example, in USD, For a fixed salary, set |
body.salary.maximumMinor | Int64 | Maximum salary in the local minor currency. |
body.salary.period | JobSalaryPeriod! | Required if you provide salary. Salary period, such as HOUR or YEAR. |
metadata.taxonomyClassification.remoteType | String | Seat location flexibility. Valid values are Fully Remote and Hybrid Remote. Omit or set to null for non-remote seats. |
metadata.taxonomyClassification.attributes | [String!] | Taxonomy attributes, such as SUIDs or strings. Maximum 100 per seat. |
metadata.campaignCategories | [String!] | Category tags for grouping related seats in campaigns. To target seats by category, write Sponsored Jobs API queries with ad_campaign_category:{category}. |
metadata.url | WebUrl | Seat-specific job URL. Overrides the parent job URL for the seat. Use it for job seeker applications if Indeed Apply is not active. |
Response — Add seats
addSeats returns an AddSeatsPayload object with a results array of AddSeatResult objects, one for each seat addition.
Each AddSeatResult includes:
| Field | Type | Description |
|---|---|---|
sourcedPostingId | ID! | Employer job UUID. Same as SourcedJobPosting.sourcedPostingId. |
employerJobId | ID! | Base64-encoded employer job IRI. Same as EmployerJob.id. |
seatPostingId | ID! | Seat identifier that you provided. |
jobKey | ID! | Encrypted ID for the seat’s JobPost. Use SeatIdentifierInput.jobKey in updateSeats and clearSeats. You can also get it from EmployerJobSeatMetaData in findEmployerJobsPartner. |
Update seats
Call updateSeats to change seat-level fields such as location, salary, taxonomy classification, campaign categories, or URL. You can use it for seats created by the ATS or by addSeats. Omit any fields you do not want to change.
To call updateSeats, you need the jobKey from the addSeats response or from EmployerJobSeatMetaData in findEmployerJobsPartner under seatsConnection.
Update seats workflow
- 1.
Get the seat's
jobKey. Get it from theaddSeatsresponse or fromEmployerJobSeatMetaDatainseatsConnectioninfindEmployerJobsPartner. - 2.
Update the seat. Call
updateSeatswith the employer job identifier, the seat'sjobKey, and the fields to change. Omit any fields that you do not want to update. Each call updates one seat. - 3.
Confirm the update. Call
findEmployerJobsPartnerand inspectseatsConnectionto verify the change. Updates usually appear within 30 seconds, but this timing is not guaranteed.
For ways to get jobKey, see Response - Add seats, findEmployerJobsPartner, and EmployerJobSeatMetaData.
For request syntax and input fields, see Request - Update seats, updateSeats, and UpdateSeatsInput.
To verify the update, see findEmployerJobsPartner and List job postings by criteria. For rate limits, see Rate limits.
Request — Update seats
Call jobsIngest.updateSeats to update a seat.
This operation requires an access token that represents the advertiser and includes these scopes:
employer_accessemployer.hosted_job
For more information about employer-scoped access tokens, see Get access token that represents employer.
Each updateSeats call updates one seat. Provide only the fields that you want to change.
Example — GraphQL request to update seats
mutation UpdateSeats { jobsIngest { updateSeats(input: { seatUpdates: [{ employerJob: { sourcedPostingId: "<SOURCED POSTING ID>" } seatIdentifier: { jobKey: "<JOB KEY>" } body: { location: { country: "US" cityRegionPostal: "Dallas, TX, 75201" streetAddress: "500 Main Street" latitude: 32.7767 longitude: -96.7970 } salary: { currency: "USD" minimumMinor: 7000000 maximumMinor: 9000000 period: YEAR } } metadata: { taxonomyClassification: { remoteType: "Hybrid Remote" attributes: ["software-engineering", "full-time"] } campaignCategories: ["dallasCampaign"] url: "https://www.example.com/jobs/dallas-123" } }] }) { results { sourcedPostingId employerJobId seatPostingId jobKey } } }}updateSeats takes one argument, input, of type UpdateSeatsInput. input includes one field, seatUpdates, which is an array of UpdateSeatInput objects. Each call updates one seat.
Each UpdateSeatInput object includes these fields:
| Field | Type | Description |
|---|---|---|
employerJob.sourcedPostingId | ID | Required. Provide exactly one of sourcedPostingId or employerJobId. UUID of the employer job that owns the seat. |
employerJob.employerJobId | ID | Required. Provide exactly one of sourcedPostingId or employerJobId. Base64-encoded IRI of the employer job. |
seatIdentifier.jobKey | ID | Required. Encrypted identifier of the seat to update. Returned by addSeats. Also available in EmployerJobSeatMetaData. |
body.location | SeatLocationInput | Updated location. Omit or set to null for no change. When provided, country and cityRegionPostal are required. |
body.salary | SeatJobPostingSalaryInput | Updated salary. Omit or set to null for no change. When provided, currency and period are required. |
metadata.taxonomyClassification | SourcedJobPostingTaxonomyInput | Updated taxonomy classification. Omit for no change. |
metadata.campaignCategories | [String!] | Updated campaign category tags. Omit for no change. Replaces the existing set of categories. |
metadata.url | WebUrl | Updated seat-specific job URL. Omit or set to null for no change. |
Response — Update seats
updateSeats returns an UpdateSeatsPayload object with a results array of UpdateSeatResult objects, one for each seat update.
Each UpdateSeatResult includes:
| Field | Type | Description |
|---|---|---|
sourcedPostingId | ID! | UUID of the employer job. |
employerJobId | ID! | Base64-encoded IRI of the employer job. |
seatPostingId | ID | Agency-provided seat identifier. null if the seat was not updated. |
jobKey | ID | Encrypted seat identifier. null if the seat was not updated. |
Clear seats
For an agency-created seat added with addSeats, clearSeats removes any updateSeats changes and expires the seat so it no longer appears in search results. For an ATS-created seat, clearSeats removes updates only. It does not expire the seat. clearSeats does not affect the parent employer job or other seats on the same job.
Clear seats workflow
- 1.
Get the seat's
jobKey. Get it from theaddSeatsresponse or fromEmployerJobSeatMetaDatainseatsConnectioninfindEmployerJobsPartner. - 2.
Clear the seat. Call
clearSeatswith the employer job identifier and the seat'sjobKey. Each call clears one seat. - 3.
Confirm the seat is cleared. Call
findEmployerJobsPartnerand inspectseatsConnectionto verify that the seat no longer appears on the employer job.
For ways to get jobKey, see Response - Add seats, findEmployerJobsPartner, and EmployerJobSeatMetaData.
For request syntax and input fields, see Request - Clear seats, clearSeats, and ClearSeatsInput.
To verify the cleared seat, see findEmployerJobsPartner and List job postings by criteria. For rate limits, see Rate limits.
Request — Clear seats
Call jobsIngest.clearSeats to expire a seat from an employer job.
This operation requires an access token that represents the advertiser and includes these scopes:
employer_accessemployer.hosted_job
For more information about employer-scoped access tokens, see Get access token that represents employer.
Each clearSeats call removes one seat.
Example — GraphQL request to expire a seat by jobKey
mutation ClearSeats { jobsIngest { clearSeats(input: { seatRemovals: [{ employerJob: { sourcedPostingId: "<SOURCED POSTING ID>" } seatIdentifier: { jobKey: "<JOB KEY>" } }] }) { results { sourcedPostingId employerJobId seatPostingId jobKey } } }}clearSeats takes one argument, input, of type ClearSeatsInput.
input includes one field, seatRemovals, which is an array of ClearSeatInput objects. Each call removes one seat.
Each ClearSeatInput object includes these fields:
| Field | Type | Description |
|---|---|---|
employerJob.sourcedPostingId | ID | Required. Provide exactly one of sourcedPostingId or employerJobId. UUID of the employer job that owns the seat. |
employerJob.employerJobId | ID | Required. Provide exactly one of sourcedPostingId or employerJobId. Base64-encoded IRI of the employer job. |
seatIdentifier.jobKey | ID | Required. Encrypted ID of the seat to remove. Returned by addSeats and available on EmployerJobSeatMetaData. |
Response — Clear seats
clearSeats returns a ClearSeatsPayload object with a results array of ClearSeatResult objects, one for each seat removal.
Each ClearSeatResult includes:
| Field | Type | Description |
|---|---|---|
sourcedPostingId | ID! | UUID of the employer job. |
employerJobId | ID! | Base64-encoded IRI of the employer job. |
seatPostingId | ID | Agency-provided seat identifier. null if it cannot be resolved. |
jobKey | ID | Encrypted seat identifier. null if the seat was not cleared. |
Rate limits
Seat-based mutations are rate limited. Space requests over time to stay within the limit.
The exact limits for addSeats, updateSeats, and clearSeats are not yet confirmed. These mutations are expected to follow the Job Update API pattern, where related mutations share one requests-per-second limit.
If you exceed the limit, you receive HTTP 429 in the HTTP status or in the GraphQL errors array. Wait briefly, then retry. If you need help, request support.
For more information about HTTP 429 and related limits, see:
Troubleshoot errors
For OAuth errors, see Troubleshoot OAuth errors.
For GraphQL errors, see Troubleshoot GraphQL errors.
You might see these errors:
FORBIDDEN: Advertiser is in a restricted moderation statusFORBIDDEN: You are missing permissions for this actionBAD_USER_INPUT:seatPostingIdalready existsUNAUTHENTICATEDBAD_USER_INPUTNOT_FOUNDDOWNSTREAM_SERVICE_ERRORorINTERNAL_SERVER_ERROR
FORBIDDEN: Advertiser is in a restricted moderation status
{ "errors": [{ "extensions": { "code": "FORBIDDEN", "message": "Advertiser is in a restricted moderation status" } }]}What it means: The advertiser linked to the OAuth token is restricted for spam protection.
What to do: This error is rare. If it occurs, contact your partner manager to remove the restriction.
FORBIDDEN: You are missing permissions for this action
{ "errors": [{ "extensions": { "code": "FORBIDDEN", "message": "You are missing permissions for this action. Ask your administrator for the permissions [Hosted_Job Create, Hosted_Job Update, Hosted_Job Read]" } }]}What it means: The user linked to the OAuth token cannot manage job seats.
What to do: If an admin did not create the OAuth client, ask an admin to grant the required permissions to the linked user. Admins can update these permissions in the UI. To manage roles and permissions, see Understanding Indeed Account Settings: User Roles & Privileges.
BAD_USER_INPUT: seatPostingId already exists
What it means: The seatPostingId in addSeats already exists on this employer job. Each seat must have a unique seatPostingId.
What to do: Use a seatPostingId that is not already used on this employer job. To update a seat, use updateSeats instead of addSeats.
UNAUTHENTICATED
{ "extensions": { "code": "UNAUTHENTICATED" }}What it means: Your OAuth token expired or is malformed.
What to do: See Troubleshoot OAuth errors.
BAD_USER_INPUT
{ "extensions": { "code": "BAD_USER_INPUT" }}What it means: Part of the request is malformed. Check the message field for details.
What to do: Fix the malformed fields. For request formats, see addSeats, updateSeats, and clearSeats.
NOT_FOUND
{ "extensions": { "code": "NOT_FOUND" }}What it means: The employer job or seat was not found.
What to do: Verify that sourcedPostingId or employerJobId is correct and that the OAuth token was requested for the correct advertiser. Verify that jobKey is correct and that the seat was not already cleared.
DOWNSTREAM_SERVICE_ERROR or INTERNAL_SERVER_ERROR
{ "extensions": { "code": "DOWNSTREAM_SERVICE_ERROR" }}{ "extensions": { "code": "INTERNAL_SERVER_ERROR" }}What it means: An internal server error occurred.
What to do: Retry later. If the error continues, contact your partner manager.