Release notes

Release notes

2021-09 | PostedPositionProfile.seekCreatedBySelfIndicator field

The SEEK API provides access to the job ads and candidate applications of your hirers. This may include job ads posted directly by a hirer on the SEEK employer website.
While we recommend handling job ads and candidate applications posted outside of your software, we understand that this isn't always possible depending on the design of your software. The new PostedPositionProfile.seekCreatedBySelfIndicator field allows you to identify whether your software posted a given job ad.
See the updated Job ads posted outside of your software documentation for guidance on using this field.

2021-08 | Hirer Relationship Changed Events

To use the SEEK API on behalf of a given hirer, you need to have the appropriate hirer relationships configured. Keeping these relationships up to date was difficult, as changes made after your initial integration had to be manually communicated by SEEK.
You might have addressed this problem by asking SEEK's customer support team for spreadsheets of the current state of relationships, or maybe by periodically querying the hiringOrganizations query and calculating a diff.
To make this easier, we've introduced a new HirerRelationshipChanged event type to the SEEK API. Like existing event types, you can create a webhook subscription that will listen for new HirerRelationshipChanged events and use them to automatically update your software.
A sample event might look like this:
JSON
Copy
{
  "events": [
    {
      "id": "eb1386f2-2364-4cf5-b85f-23b8de1c2b80",
      "createDateTime": "2021-08-04T01:03:35.610Z",

      "type": "HirerRelationshipChanged",

      "hirerId": "seekAnzPublicTest:organization:seek:93WyyF1h"
    }
  ],
  "subscriptionId": "seekAnzPublicTest:webhookSubscription:events:RNzsabxEX56cuRepCD9A8j"
}
You can then use the hirerId contained in the event to get the current state of the relationship from the hiringOrganization query.
For more information, see the events section of the hirer relationships documentation.

2021-08 | CandidateProfile.associatedPositionProfile field

When retrieving a candidate profile from the SEEK API, you typically need to select additional fields to attribute it to a position and hirer. Previously, this required indirection through the CandidateProfile.associatedPositionOpenings field, which was generally unintuitive and inefficiently retrieved all position profiles for the opening.
The new CandidateProfile.associatedPositionProfile field allows you to directly select the position associated with a candidate application or profile purchase:
Diff
Copy
query ($id: String!) {
      candidateProfile(id: $id) {
+       associatedPositionProfile {
-       associatedPositionOpenings {
-         positionOpening {
-           positionProfiles {
              seekHirerJobReference
              positionOrganizations {
                id {
                  value
                }
              }
-             positionUri
-           }
-         }
-         positionUri
        }
      }
    }

2021-07 | Ad Selection theme

We’ve extended the Ad Selection Panel to support custom styling via theme properties provided to the render method. All the theme properties are optional and have default values.
Here is an example of how to specify the theme properties:
JavaScript
Copy
SEEKAdPostingWidget.render(document.getElementById('seekWidget1ContainerDiv'), {
  mode: 'Create',
  onChange: function (event) {},
  getAuthToken: function () {
    return Promise.resolve('browser token');
  },
  draftAdvertisement: {
    positionTitle: 'Developer',
    jobCategoryId: 'seekAnzPublicTest:jobCategory:seek:2EFstqFvP',
    positionLocationId: 'seekAnzPublicTest:location:seek:2TCFhBtw9',
    hirerJobReference: 'Premium521',
  },
  theme: {
    selectionColor: '#e82b85',
    linkColor: '#e82b85',
    contractBarPrimaryBackgroundColor: '#e82b85',
    contractBarSecondaryBackgroundColor: '#f6d2e0',
    errorColor: '#ff0000',
  },
});
See the Ad Selection Panel page for more information on each theme property.

2021-06 | self query

We’ve added a self query to return information about an access token’s organizations.
This can be a convenient alternative to the hiringOrganization query when working with browser tokens. Instead of needing to specify the hirer ID explicitly, it returns the SEEK hirer the browser token is scoped to.
QueryResult
query {
  self {
    hirer {
      id {
        value
      }
      name
      seekApiCapabilities {
        relationshipTypeCodes
        applicationMethodCodes
      }
    }
  }
}

2021-06 | Application questionnaire input limits

We’ve tightened the UTF-8 byte limit on String input fields of the createApplicationQuestionnaire mutation from 2,048 to 255 bytes. This aligns with the default limit for the SEEK API.

2021-05 | Update webhook subscription conflicts

We’ve introduced a new result format for updateWebhookSubscriptionDeliveryConfiguration .
Webhook subscriptions must be unique based on their schemeId, eventTypeCode, hirerId & url. Attempting to create a duplicate webhook subscription with createWebhookSubscription would correctly return a mutation conflict. However, we didn’t properly handle updating a webhook subscription’s url to conflict with an existing subscription.
💥 If your software uses updateWebhookSubscriptionDeliveryConfiguration , update the selection set to account for this change:
GraphQL
Copy
mutation($input: UpdateWebhookSubscriptionDeliveryConfigurationInput!) {
  updateWebhookSubscriptionDeliveryConfiguration(input: $input) {
-     webhookSubscription {
-       url
-       maxEventsPerAttempt
-     }
+     __typename
+     ... on UpdateWebhookSubscriptionDeliveryConfigurationPayload_Success {
+       webhookSubscription {
+         url
+         maxEventsPerAttempt
+       }
+     }
+     ... on UpdateWebhookSubscriptionDeliveryConfigurationPayload_Conflict {
+       conflictingWebhookSubscription {
+         id {
+           value
+         }
+       }
    }
  }
}
Then, update your code to check for conflicts in one of two new ways:
JavaScript
Copy
if (
   // Option 1: test for existence of conflict field
   response.data?.conflictingWebhookSubscription
   // Option 2: test name of the response type
   response.data?.__typename === 'CreateWebhookSubscriptionDeliveryConfigurationPayload_Conflict'
) {
  // Handle conflict here
}

2021-05 | Ad selection panel now returns paymentDueExcludingTax

We’ve introduced a paymentDueExcludingTax property in the onChange callback for the Ad Selection Panel which will give your software the pricing information needed to update invoicing systems if necessary.
JavaScript
Copy
SEEKAdPostingWidget.render(document.getElementById('seekWidget1ContainerDiv'), {
  mode: 'Create',
  onChange: function (event) {
    const { advertisement } = event;
    const { paymentDueExcludingTax } = advertisement;

    console.log(`$${paymentDueExcludingTax.value} ${paymentDueExcludingTax.currency} will be invoiced to the hirer`);
  },
  getAuthToken: function () {
    return Promise.resolve('browser token');
  },
  draftAdvertisement: {...},
});

2021-05 | Hirer configuration queries

SEEK’s support team configures hirers to work with the SEEK API:
Previously, integration partners haven’t had visibility into SEEK’s configuration. This required manual reconciliation with our partners to avoid unexpected failures.
We've extended the SEEK API to support automatically detecting hirer configuration:
  • The HiringOrganization object now has a seekApiCapabilities field. This allows you to query a hirer’s active relationships and allowed application methods.
    You can retrieve a HiringOrganization using either the hiringOrganization query or the seekAnzAdvertiser query :
    By hirer IDBy SEEK advertiser ID
    query ($id: String!) {
      hiringOrganization(id: $id) {
        name
        seekApiCapabilities {
          relationshipTypeCodes
          applicationMethodCodes
        }
      }
    }
  • The new hiringOrganizations query allows you to list all the SEEK hirers you have an active relationship with. You can optionally filter on the hirer’s name or specific relationship types.
    You can use this to retrieve a complete list of your SEEK hirers, including their identifiers and API capabilities:
    QueryVariablesResult
    query ($schemeId: String!, $first: Int) {
      hiringOrganizations(schemeId: $schemeId, first: $first) {
        edges {
          node {
            id {
              value
            }
            name
            seekApiCapabilities {
              relationshipTypeCodes
              applicationMethodCodes
            }
          }
        }
        pageInfo {
          hasNextPage
          endCursor
        }
      }
    }

2021-04 | PositionProfileClosed event

We’ve added a new PositionProfileClosed event that’s emitted whenever a job ad has been closed.
It’s recommended that you update your software’s internal state based on PositionProfileClosed events instead of scheduled end dates. This ensures your software remains synchronised with SEEK, particularly when a hirer contacts our Customer Service team to close a job ad early.
This is an example of a webhook body containing a PositionProfileClosed event:
JSON
Copy
{
  "events": [
    {
      "id": "seekAnzPublicTest:event:events:KNYEK91zoe8JoZU9iv6853",
      "type": "PositionProfileClosed",

      // The date the job ad was closed
      "createDateTime": "2019-08-20T21:02:27.101Z",

      // This can be passed to the `positionProfile` query to retrieve the job ad's final state
      "positionProfileId": "seekAnzPublicTest:positionProfile:jobAd:2782PZfXV"
    }
  ],
  "subscriptionId": "seekAnzPublicTest:webhookSubscription:events:GykAb69qu1CwbaRLsuAsVf"
}

2021-04 | Webhook subscription mutation split

It's currently possible to update some config options of a webhook subscription, using the updateWebhookSubscriptionDeliveryConfiguration mutation. Previously, this mutation required the signingAlgorithmCode as a mandatory string, which in turn required that secret was also provided, which meant that the secret string for webhook signing had to be included in every request to update a webhook subscription. This made simple updates like changing the maxAttemptsPerEvent unnecessarily difficult.
To allow easier update actions, this mutation has been split in two. To update maxEventsPerAttempt or url, continue to use the updateWebhookSubscriptionDeliveryConfiguration mutation as before. To make changes to the signing config options, use the new updateWebhookSubscriptionSigningConfiguration mutation, which takes the signingAlgorithmCode and secret options. Both mutations still return the full WebhookSubscription .
New mutation examples
Update delivery configUpdate signing config
mutation updateWebhookDeliveryConfig(
  $webhookSubscriptionId: String!
  $url: String!
  $maxEventsPerAttempt: Int
) {
  updateWebhookSubscriptionDeliveryConfiguration(
    input: {
      webhookSubscription: {
        id: $webhookSubscriptionId
        url: $url
        maxEventsPerAttempt: $maxEventsPerAttempt
      }
    }
  ) {
    webhookSubscription {
      id {
        value
      }
      url
      maxEventsPerAttempt
    }
  }
}

2021-04 | Mutation input field limits

A default maximum length for String mutation input fields is now documented in the SEEK API’s request limits. Exceptions to this default are explicitly documented in the GraphQL schema .
An operation containing an input field that exceeds its length limit will receive a BAD_USER_INPUT error response.

2021-04 | Ad Selection message visibility

When ad products are shown to hirers, they may have associated messages. Some messages should always be visible and others should be visible when a product is selected or not selected.
The seekAnzHirerAdvertisementCreationProducts , seekAnzHirerAdvertisementModificationProducts and seekAnzHirerAdvertisementModificationProductsAlt queries have been extended to include a field named visibilityCode. It has 3 possible values:
  1. Always indicates that the message should always be visible.
  2. ProductSelected indicates that the message should be visible when the product is selected.
  3. ProductNotSelected indicates that the message should be visible when the product is not selected.
QueryVariablesResult
query seekAnzHirerAdvertisementProducts(
  $advertisementId: String!
  $hirerId: String!
  $draftAdvertisement: SeekAnzAdProductAdvertisementDraftInput!
) {
  seekAnzHirerAdvertisementModificationProducts(
    hirerId: $hirerId
    advertisementId: $advertisementId
    draftAdvertisement: $draftAdvertisement
  ) {
    name
    messages {
      visibilityCode
      content
    }
  }
}

2021-03 | Ad Selection features

We’ve extended Ad Selection to indicate when an ad product supports branding and search bullets.
This allows you to update your UI to either show or hide your branding and search bullet fields depending on the selected product:
  • Query the graph for ad product features.
    QueryVariablesResult
    query seekAnzHirerAdvertisementProducts(
      $advertisementId: String!
      $hirerId: String!
      $draftAdvertisement: SeekAnzAdProductAdvertisementDraftInput!
    ) {
      seekAnzHirerAdvertisementCreationProducts(
        hirerId: $hirerId
        draftAdvertisement: $draftAdvertisement
      ) {
        ...productFields
      }
    
    
      seekAnzHirerAdvertisementModificationProducts(
        hirerId: $hirerId
        advertisementId: $advertisementId
        draftAdvertisement: $draftAdvertisement
      ) {
        ...productFields
      }
    }
    
    
    fragment productFields on SeekAnzAdProduct {
      name
      features {
        brandingIndicator
        searchBulletPointsIndicator
      }
    }
  • Get the features in the onChange event raised by the Ad Selection Panel.
    JavaScript
    Copy
    SEEKAdPostingWidget.render(document.getElementById('seekWidget1ContainerDiv'), {
      mode: 'Create',
      onChange: function (event) {
        const { advertisement } = event;
        const { features } = advertisement;
        const { brandingIndicator, searchBulletPointsIndicator } = features;
    
    
        console.log(`Branding ${brandingIndicator : 'is' : 'is not'} supported`);
        console.log(`Search bullet points ${searchBulletPointsIndicator : 'are' : 'are not'} supported`);
    
    
        if (brandingIndicator) {
          // show field to select a brand
        } else {
          // hide brand field
        }
        if (searchBulletPointsIndicator) {
          // show fields to enter bullet points
        } else {
          // hide bullet point fields
        }
      },
      getAuthToken: function () {
        return Promise.resolve('browser token');
      },
      draftAdvertisement: {
        positionTitle: 'Developer',
        jobCategoryId: 'seekAnzPublicTest:jobCategory:seek:2EFstqFvP',
        positionLocationId: 'seekAnzPublicTest:location:seek:2TCFhBtw9',
        hirerJobReference: 'Premium521',
      },
    });

2020-12 | Replay webhook subscriptions

We’ve extended webhook subscriptions with a new mutation replayWebhookSubscription , which resends undelivered events from the past 90 days to your subscription endpoint.
This makes it easy for you to recover from downtime without building a bespoke replay mechanism on top of the events query.
MutationVariables
mutation ($input: ReplayWebhookSubscriptionInput!) {
  replayWebhookSubscription(input: $input) {
    webhookSubscription {
      id {
        value
      }
    }
  }
}

2020-11 | Profile Apply extensions

We’ve extended Profile Apply with additional structured information on the CandidateProfile object:
  • certifications lists certifications and licences the candidate holds.
  • education.descriptions adds free-text descriptions to the candidate’s education history.
QueryVariables
query ($id: String!) {
  candidateProfile(id: $id) {
    education {
      descriptions
    }
    certifications {
      name
      issuingAuthority {
        name
      }
      issued
      effectiveTimePeriod {
        validTo
      }
      descriptions
    }
  }
}

2020-09 | Mutation conflicts

We’ve introduced a new result format for mutations that create objects. This enables explicit handling of mutation conflicts.
💥 The new format can be seen in the createWebhookSubscription and uploadCandidate mutations. If your software creates webhook subscriptions, update the selection set to account for this change:
GraphQL
Copy
mutation($input: CreateWebhookSubscriptionInput!) {
  createWebhookSubscription(input: $input) {
-     webhookSubscription {
-       id {
-         value
-       }
-     }
+     __typename
+     ... on CreateWebhookSubscriptionPayload_Success {
+       webhookSubscription {
+         id {
+           value
+         }
+       }
+     }
+     ... on CreateWebhookSubscriptionPayload_Conflict {
+       conflictingWebhookSubscription {
+         id {
+           value
+         }
+       }
    }
  }
}
Then, update your code to check for conflicts in one of two new ways:
JavaScript
Copy
if (
-   // Old: make assumptions based on semi-structured errors array
-   response.errors?.some(
-     (err) =>
-       err.extensions.code === 'BAD_USER_INPUT' &&
-       err.message === 'Subscription already exists',
-   )
+   // Option 1: test for existence of conflict field
+   response.data?.conflictingWebhookSubscription
+   // Option 2: test name of the response type
+   response.data?.__typename === 'CreateWebhookSubscriptionPayload_Conflict'
) {
  // Handle conflict here
}

2020-08 | Job Posting features

We’ve extended Job Posting with two new features:
  • Query the nearest locations for a geolocation.
    This enables you to suggest a SEEK location closest to an end user, or map to a SEEK location from an internal location hierarchy.
    QueryVariables
    query ($first: Int, $geoLocation: GeoLocationInput!, $schemeId: String!) {
      nearestLocations(
        first: $first
        geoLocation: $geoLocation
        schemeId: $schemeId
      ) {
        contextualName
      }
    }
  • Query the brands available for StandOut and Premium ads.
    This enables you to include a brand preview and picker in your posting flow.
    QueryVariables
    query ($after: String, $first: Int, $hirerId: String!) {
      advertisementBrandings(after: $after, first: $first, hirerId: $hirerId) {
        edges {
          node {
            name
          }
        }
      }
    }

2020-08 | Documentation updates

We’ve extended our documentation with new guidance:
  • GraphQL usage
    We’ve documented some introductory concepts specific to the SEEK API, along with general guidance for using GraphQL in your software. This includes strong advice around using GraphQL variables to prevent code injection.
  • We’ve included advice on reprocessing “duplicate” webhooks. In the rare event of a data quality issue, this allows SEEK to perform remediation and prompt your software to automatically re-retrieve the affected objects.
  • SEEK Application Export migration
    We’ve described a new transition approach that allows you to run both integrations simultaneously and de-duplicate applications rather than candidates. We’ve also noted some potential gotchas.
We’ve refreshed our schema documentation site! It should be easier to navigate and drill down into nested objects.

2020-07 | Profile Apply

When a candidate applies on SEEK through a supported channel, they consent to share a snapshot of their SEEK profile with the hirer.
We’ve extended Application Export with more structured information on the CandidateProfile object:
  • employment includes multiple roles from the candidate’s employment history, along with a description of their achievements and responsibilities in each role.
  • education describes educational programmes that the candidate has completed and is currently pursuing.
  • qualifications lists the self-asserted skills of the candidate.
QueryVariables
query ($id: String!) {
  candidateProfile(id: $id) {
    employment {
      positionHistories {
        descriptions
      }
    }
    education {
      educationDegrees {
        name
      }
    }
    qualifications {
      competencyName
    }
  }
}
See our documentation on exporting candidate profiles for more information.