GraphQL in practice

The SEEK API’s GraphQL endpoint is served over HTTPS. It requires TLS 1.2  or newer, and does not support weak ciphers from TLS 1.0 and TLS 1.1.The SEEK API requires an application/json content type for HTTP POST requests to its GraphQL endpoint. It may support additional content types like application/graphql+json  once they have been stabilised in a GraphQL specification.Response and request bodies in the SEEK API are encoded in UTF-8.

Operations

The SEEK API supports two types of GraphQL operations: queries and mutations.
  • Queries allow you to read objects.
  • Mutations allow you to create, update and delete objects.
This developer site provides guided examples of the queries and mutations relevant to each use case. A comprehensive lower-level listing is available in our schema documentation.The SEEK API does not make use of GraphQL subscriptions and instead provides webhooks.

Variables

If your operation has arguments, they must be parameterised with variables. This is analogous to SQL prepared statements  and prevents code injection .
RequestResponse
Copy
POST https://graphql.seek.com/graphql HTTP/1.1
Accept-Language: en-AU
Authorization: Bearer PARTNER_TOKEN_HERE
Content-Type: application/json
User-Agent: example-application/1.2.3
X-Request-Id: 6837f359-0335-418c-80dc-9d98a57a330c
X-Session-Id: b5a8774c-c450-4906-a25c-861bce129106
{
  "query": "query($id: String!) { hiringOrganization (id: $id) { name } }",
  "variables": {
    "id": "seekAnzPublicTest:organization:seek:93WyyF1h"
  }
}
See the official GraphQL documentation  for more details.

Optional arguments

When an input argument is optional and unused, your software should omit it:
GraphQL
# Inline arguments are only used to simplify these examples.
# Parameterise your production queries as per advice above.

# Correct: omit unused argument
query Omit {
  events(schemeId: "seekAnzPublicTest") {
    pageInfo {
      hasNextPage
    }
  }
}

# Incorrect: set unused argument to null
query Null {
  events(schemeId: "seekAnzPublicTest", after: null) {
    pageInfo {
      hasNextPage
    }
  }
}

# Incorrect: set unused argument to an empty string
query EmptyString {
  events(schemeId: "seekAnzPublicTest", after: "") {
    pageInfo {
      hasNextPage
    }
  }
}

# Incorrect: create variable for unused argument
query Variable($after: String!) {
  events(schemeId: "seekAnzPublicTest", after: $after) {
    pageInfo {
      hasNextPage
    }
  }
}
While some incorrect options may seemingly produce an identical response from the SEEK API, your software should always omit unused arguments for two reasons:
  1. Changes to our GraphQL schema will be less likely to affect your software.Consider a scenario where your software does not use the last argument in the query above and it is deprecated from the SEEK API. If you already omit the argument, the deprecation will not impact your software. On the other hand, if you specify the argument and set it to null or a variable, you will have to manually update your software to remove it from your requests, which is pure busywork.
  2. Requests will be smaller and faster.
This same advice applies to nested input fields that are parameterised with variables:
QueryVariables (correct)Variables (incorrect null)Variables (incorrect empty string)
query ($filter: EventsFilterInput!, $schemeId: String!) {
  events(filter: $filter, schemeId: $schemeId) {
    pageInfo {
      hasNextPage
    }
  }
}

Selecting fields

GraphQL provides fine-grained control over which fields should be returned in a response. This allows your software to clearly express its requirements and the SEEK API to optimise responses by only providing the fields required.For example, to simply retrieve the display name of a location, you can use the location query with the following selection set:
QueryVariablesResult
query ($id: String!) {
  location(id: $id) {
    contextualName
  }
}
To determine a corresponding currency, you can use the same query but a different selection set:
QueryVariablesResult
query ($id: String!) {
  location(id: $id) {
    currencies {
      code
    }
  }
}
Your software should only select the fields it currently uses for three reasons:
  1. It allows SEEK to get a better understanding of your requirements.If we have an accurate view of your feature adoption, we can have more informed conversations about your integration and better accommodate your needs when considering future changes.
  2. Changes to our GraphQL schema will be less likely to affect your software.Consider a scenario where your software does not use the hasPreviousPage field in the query above and it is deprecated from the SEEK API. If you already omit the field, the deprecation will not impact your software. On the other hand, if you select the field, you will have to manually update your software to remove it from your requests, which is pure busywork.
  3. Requests and responses will be smaller and faster.

Tracing requests

When an issue is found with an integration it’s sometimes difficult to determine which services were involved and how they interacted. SEEK has conventions for additional HTTP request headers to help trace issues across both our systems. These conventions should be used for all server-to-server HTTP requests including GraphQL queries, token requests and attachment file downloads. Some of these conventions should also be applied to requests made from the browser using browser tokens.
  • User-Agent  is a standard HTTP header identifying the service making an HTTP request.While your HTTP client library likely already sends a generic User-Agent, it’s recommended that you override this to include the name and version of the service making the request. This makes it easier to trace a request back to a specific deployment of your software when debugging an issue.Requests made from the browser do not need to alter this header.
  • The X-Request-Id header is a SEEK extension for tracing a request as it passes through our systems.It’s recommended that you generate a UUID  for each HTTP request and record that UUID along with any relevant log entries.Requests made from the browser should add this header.
  • The X-Session-Id header is a SEEK extension for grouping requests in the same interaction together. This helps isolate the sequence of events that led to an issue or error, especially for high traffic integrations.It’s recommended that you generate a UUID for each session and record that UUID along with any relevant log entries. You can choose which definition of session makes the most sense for you. For example, an interactive user flow for creating & posting a job ad might be assigned a single session. Or, every time your webhook endpoint is called you might generate a new session for all HTTP requests related to processing its events.Requests made from the browser should add this header.

Example request

HTTP
Copy
POST /graphql HTTP/1.1
User-Agent: example-application-import-webhook/1.2.3
X-Request-Id: 749fbdc9-fbe5-40e9-b5ae-fee3375f1e28
X-Session-Id: 6eb0559a-cb91-4178-bb3d-9f5513e9ecc2

Content localisation

The SEEK API supports content localisation in select GraphQL operations and SEEK-hosted panels. It currently supports the following languages:
Language
Locale
Australian English
en-AU
Bahasa Indonesia
id-ID
Thai
th-TH
As seen in the previous section, you may set the standard Accept-Language  HTTP request header to indicate the language preferences of the end user. A web browser will automatically set this header on client-side requests to the SEEK API that use a browser token, but you will need to manually propagate the preference for requests that originate from your software’s backend.The Accept-Language  header drives translations for user-facing response fields, such as copy for an interactive Job Posting feature like ad selection. The language(s) of the response may be described in the standard Content-Language  HTTP response header, falling back to Australian English (en-AU) where translations for the preferred languages are not available.Unsupported region subtags of a language will resolve to a supported subtag. For example, consider the following Accept-Language  header:
HTTP
Copy
Accept-Language: en-US,th;q=0.9,
While the SEEK API does not support the top preference in this header, US English (en-US), it will fall back to the associated primary language (en) and return content in Australian English (en-AU). This fallback is evaluated ahead of the second preference of Thai (th).

Client libraries and tools

While the SEEK API works fine with plain old HTTP clients, you can make the most of it with the GraphQL ecosystem .
  • GraphQL client libraries  make it easier to compose and run GraphQL operations.They are available in a variety of popular languages, including C#, Go, Java and JavaScript.
  • GraphQL tooling  includes code generators to automatically declare models and types in your language from the SEEK API’s schema.The SEEK API validates responses against its own schema at runtime, so you should never receive a non-conforming response.

Request batching

The SEEK API provides a couple of ways to batch multiple operations in a single request. This may be desirable to reduce HTTP overhead in certain scenarios, like processing a batch of events received via webhook.There are a couple of caveats, however:
  1. Higher response latency can be expected as a batch is constrained by its slowest operation.
  2. Operations are not processed atomically across the batch, so you’ll need to handle partial failures.

GraphQL field-based batching

GraphQL has built-in support for bundling multiple fields into a single operation:
QueryVariablesResult
query ($hirerId: String!, $jobCategoryId: String!) {
  # first query field
  hiringOrganization(id: $hirerId) {
    name
  }

  # second query field
  jobCategory(id: $jobCategoryId) {
    name
  }
}
This approach has the following characteristics:
  • Queries and mutations cannot be combined in a request.
  • Queries are executed concurrently.
  • Mutations are executed sequentially .This can be useful to avoid race conditions when applying multiple changes to an object, like updating both the contacts and status of a given position opening.
  • Broad support in GraphQL client libraries as it’s part of the specification .

HTTP request-based batching

The SEEK API also lets you pack multiple operations into the request body. The response body will contain an array of results in the same order.
JSON
Copy
[
  {
    "query": "query($id: String!) { hiringOrganization (id: $id) { name } }",
    "variables": {
      "id": "seekAnzPublicTest:organization:seek:93WyyF1h"
    }
  },
  {
    "query": "query($id: String!) { jobCategory (id: $id) { name } }",
    "variables": {
      "id": "seekAnzPublicTest:jobCategory:seek:27HXTkNXh"
    }
  }
]
This approach has the following characteristics:
  • Queries and mutations can be combined in a request.
  • Queries are executed concurrently.
  • Mutations are executed concurrently.This can be useful to speed up independent operations, like uploading two separate candidates.
  • Limited support in GraphQL client libraries.

Request caching

Some GraphQL client libraries support client-side caching and may even enable it by default. For example, the Apollo Client for JavaScript  prescribes an in-memory cache that assumes each object has a unique id or _id string field.Such heuristics may not work well with the SEEK API, which uses a non-primitive object identifier and various field names from HR-JSON:
JSON
Copy
{
  "profileId": {
    "value": "seekAnzPublicTest:candidateProfile:apply:7DtW6Q68gk2R4okNGhvg1Y"
  }
}
We recommend that you disable client-side caching for machine-to-machine requests. For requests that originate from a browser or mobile app, client libraries like Apollo Client may allow you to customise the cache , or turn it off:
JavaScript
Copy
new ApolloClient({
  defaultOptions: {
    query: {
      fetchPolicy: 'no-cache'
    },
    watchQuery: {
      fetchPolicy: 'no-cache'
    }
  }
});