SEEK API conventions

    Suggestions will appear below the field as you type

    SEEK API conventions

      Suggestions will appear below the field as you type

      SEEK API conventions

      SEEK API conventions

      This document describes conventions the SEEK API uses on top of standard GraphQL. To learn more about standard GraphQL we recommend the GraphQL Foundation’s Introduction to GraphQL .

      Scheme identifiers

      Scheme identifiers indicate the context that a given object belongs to. They are predominantly seen in object identifiers, and are also input into certain operations to specify the data source that information is being requested from or submitted to.
      There are four scheme IDs currently in use:
      • seekAnz and global are used for real data in our production environment.
        The difference between these schemes is opaque to partners and largely vestigial; seekAnz objects were adapted from existing Australia & New Zealand infrastructure, while global objects were designed from the start to be used across the SEEK Group.
        Our documentation and examples should make it clear which scheme applies in the context of a given object or operation.
      • seekAnzPublicTest and globalPublicTest are used for mock data in our Playground environment.
        Separate scheme IDs are used to reduce confusion with our production environment.
      The SEEK API has been enabled across our Asia-Pacific (APAC) markets in a backward compatible manner, where objects and operations continue to use existing schemes without breaking changes. You will see the seekAnz scheme used when posting jobs and receiving applicants throughout APAC; don’t dwell on the vestigial Anz portion of the scheme ID.

      Object identifiers

      Object identifiers (OIDs) uniquely identify each object in the SEEK API. The ObjectIdentifier  type in our schema mirrors the equivalent HR-JSON concept and looks something like this:
      JSON
      Copy
      {
        "value": "globalPublicTest:candidate:uploaded:12uNaG7vqgW6G1jG7qp7ha"
      }
      You may notice that the object identifier is prefixed by a scheme identifier and appears to be mostly human readable. While it may be tempting to try to parse meaning out of the segments, they are an implementation detail. Object identifiers must be treated as opaque strings and stored in their entirety; do not disassemble or decode them.
      There are a couple of benefits that these string identifiers have over traditional numeric identifiers:
      1. They are designed for forward compatibility.
        Small integer fields run the risk of overflowing; numeric identifiers used in our prior integrations almost reached the limit of the positive range for a signed 32-bit integer. Object identifiers require more expansive data types from the get go and are carefully namespaced. This allows us to extend the SEEK API to additional markets without making breaking changes to its identifiers.
      2. They are not easily guessable.
        A sequential numeric identifier makes it trivial to exploit insufficient access control  with insecure direct object references . This also means that object identifiers are not ordered. Assumptions made around sequential ordering are often hard to meet when working with increasingly distributed and parallel systems. For scenarios such as pagination, the SEEK API opts to use opaque cursors.
      Object identifiers have a maximum length of 255 characters, and will always be representable with 255 bytes in UTF-8  encoding. You can rely on this limit to appropriately size your storage solution.

      Cursor-based pagination

      The SEEK API adheres to the Relay GraphQL Cursor Connections Specification , which prescribes a standard way of expressing paginated queries and results. This includes bidirectional pagination, so you can request either the previous page or the next page of results from a given point:
      QueryVariables
      query ($cursor: String!, $limit: Int!, $schemeId: String!) {
        justAfter: events(after: $cursor, first: $limit, schemeId: $schemeId) {
          # The two events that occurred just after the specified event
          edges {
            node {
              createDateTime
              typeCode
            }
          }
      
      
          # Information used to query the next page of events, if any
          pageInfo {
            hasNextPage
            endCursor
          }
        }
      
      
        justBefore: events(before: $cursor, last: $limit, schemeId: $schemeId) {
          # The two events that occurred just before the specified event
          edges {
            node {
              createDateTime
              typeCode
            }
          }
      
      
          # Information used to query the previous page of events, if any
          pageInfo {
            hasPreviousPage
            startCursor
          }
        }
      }
      Cursors allow us to identify an exact point in the dataset to resume pagination from. By contrast, while offset-based pagination may seem more intuitive, the offsets will shift whenever a new item is added or removed from the dataset. This can lead to items being skipped or duplicated across pages, which is undesirable for critical workflows like retrieving pages of candidate applications.

      Enums and code lists

      While GraphQL’s built-in enumeration type  works well with a frozen set of values, adding a new value to an existing enum can be a breaking change to certain client implementations.
      The SEEK API prefers to represent non-frozen enumerations as GraphQL strings. These fields typically have a Code suffix and map to code lists defined by HR-JSON. The currently defined values are documented in their descriptions, but you should write your code to gracefully handle future additions.
      For example, the PositionOpening  object contains a statusCode field that includes the following in its description:
      Currently, three codes are defined:
      • Incomplete indicates the position opening is in a draft state.
      • Active indicates the position opening is open.
      • Closed indicates the position opening has been closed.
      The SEEK API also provides a code list field on each interface . You can use this code list field or the __typename  meta field to trigger different branches in your code.
      For example, the Event  interface defines a typeCode field:
      QueryVariables
      query ($id: String!) {
        # Returns an Event interface
        event(id: $id) {
          # Meta field indicates the concrete type
          __typename
      
      
          # Code list field indicates the concrete type
          typeCode
      
      
          # GraphQL fragment can select fields from the concrete type
          ... on PositionProfilePostedEvent {
            positionProfileId
          }
        }
      }

      Mutation conflicts

      Mutations that create objects in the graph may not succeed due to unique constraints or idempotency. Such mutations may result in a success or conflict, collectively represented by a GraphQL union .
      For example, the createWebhookSubscription mutation  has the following return type:
      GraphQL
      union CreateWebhookSubscriptionPayload =
        | CreateWebhookSubscriptionPayload_Conflict
        | CreateWebhookSubscriptionPayload_Success
      Make sure to check the result of a mutation instead of assuming that it succeeded. You can use inline fragments  to select fields from each union member. The conflict result allows you to identify and describe the existing object that conflicts with your operation.
      The conflict result includes the existing object under a distinct field name that starts with conflicting. You can either check for the existence of this field or query the __typename  meta field to trigger different branches in your code.
      MutationVariables
      mutation ($input: CreateWebhookSubscriptionInput!) {
        createWebhookSubscription(input: $input) {
          # Meta field indicates the concrete type
          __typename
      
      
          ... on CreateWebhookSubscriptionPayload_Conflict {
            # Details of the existing webhook subscription that conflicts
            conflictingWebhookSubscription {
              id {
                value
              }
              createDateTime
            }
          }
      
      
          ... on CreateWebhookSubscriptionPayload_Success {
            # Details of the newly created webhook subscription
            webhookSubscription {
              id {
                value
              }
              createDateTime
            }
          }
        }
      }

      Request limits

      Text encoding

      Request bodies should be UTF-8  encoded and comprise code points  from the character range in the W3C’s XML 1.0 Specification .
      Invalid data and unsupported code points may be stripped or substituted with the Unicode replacement character � .

      String lengths

      Each String  input field on a mutation has a maximum length.
      The SEEK API measures these lengths in Unicode characters; you may also see these more precisely referred to as Unicode code points  or UTF-32  code units . This method of measurement is a closer match to the graphemes visible to an end user, and is easy to replicate in a web browser or your programming language of choice.
      C#JavaJavaScriptKotlinPHPSwift
      Copy
      using System.Linq;
      
      
      var input = "👩‍👩‍👧‍👧";
      
      
      input.EnumerateRunes().Count();
      // 7
      Your software may transpose this limit to UTF-16  code units, which is how the HTML maxlength attribute  and JavaScript String.length property  perform their measurements. However, this will impose a stricter limit than necessary on your hirers.
      HTMLJavaScript
      Copy
      <input maxlength="10" value="👩‍👩‍👧‍👧" />
      <!-- 11 -->
      By default, a String input field has a maximum length of 255 characters. Any input field that deviates from this default will contain explicit documentation in the GraphQL schema , like so:
      This field has a maximum length of 1,000 characters.
      In a couple of exceptional cases, a String  input field may have a stricter limit expressed in bytes in UTF-8  encoding. This currently applies to email addresses , object identifiers, and HMAC secret keys for webhook signing. SEEK will never send you an object identifier that does not satisfy this limit, but other input fields of this nature will contain explicit schema documentation:
      This field has a maximum length of 256 bytes in UTF-8 encoding.
      You can interactively view the difference between these measurements below:
      JavaScript
      Copy
      const input = "👩‍👩‍👧‍👧";
      
      
      new TextEncoder().encode(input).length;
      // 25 bytes in UTF-8 encoding
      
      
      input.length;
      // 11 UTF-16 code units
      
      
      Array.from(input).length;
      // 7 characters

      Operation complexity

      Operations should not select an overly-complex set of response fields.
      The SEEK API exposes an interconnected graph of objects, so it’s possible to ask for a deeply nested fields and/or a large number of fields. Operations that are deemed too complex will be blocked.
      This limit is in place to prevent denial of service attacks ; you shouldn’t bump into it in regular use.

      HTTP request batching

      A batched HTTP request may contain up to 10 operations.
      This matches the maximum number of events that we will send to your software in a single webhook. HTTP requests that contain more than 10 operations will be blocked.
      GraphQL field-based batching has no hard limit on the number of fields. However, each field contributes towards the operation’s complexity limit.

      Event polling

      The events query  should not be polled for new events more than once a minute.
      Webhooks should be used if you require low latency or deal with high volumes.