Async Operations

Some Oyster REST API endpoints perform work that takes longer than a single HTTP round‑trip can reasonably hold open (for example, creating a draft employment or an expense). For those endpoints, the API uses an async operation pattern: it acknowledges your request immediately with an identifier, performs the work in the background, and lets you discover the result either by polling or via a webhook.

This guide explains:

  • What an operation is
  • How to tell when an endpoint is async
  • The immediate async response format
  • How to retrieve the final result (polling and webhook)
  • Error handling
  • Idempotency and tracing

1. What is an "operation"?

Every request to the Oyster REST API creates an Operation record, regardless of whether the endpoint is synchronous or asynchronous. An operation represents:

  • The request (method, path, query params, body)
  • The response (status code, data, errors)
  • Whether the work is completed and whether it successful

Each operation has a unique key (a UUID7) that is always returned to you in a custom response header:

X-Oyster-Operation-Key: <operation_key>

You can rely on this header being present on every response from the API. For synchronous endpoints you usually won't need it — you already have the result in the body — but you can use it for auditing, support tickets, and tracing.

For async endpoints, this same key is how you look up the eventual result.

2. How to tell an endpoint is async

An endpoint is async when it responds with HTTP 202 Accepted and a body of the form:

{
  "meta": {
    "operationKey": "e1d8b6c7-be75-c625-ea2d-b10de17aa688"
  }
}

Async endpoints are documented as such in API reference. Examples of async endpoints at the time of writing include:

  • POST /{version}/hiring/employments/draft
  • POST /{version}/expenses/expenses

Note: the body of a 202 response contains only meta.operationKey — there is no business data. Do not attempt to parse the created resource from this response.

3. The async response contract

HTTP/1.1 202 Accepted
Content-Type: application/json
X-Oyster-Operation-Key: e1d8b6c7-be75-c625-ea2d-b10de17aa688

{
  "meta": {
    "operationKey": "e1d8b6c7-be75-c625-ea2d-b10de17aa688"
  }
}

Key points:

  • 202 means "accepted for processing", not "succeeded".
  • The operationKey in the body and X-Oyster-Operation-Key header are the same value.
  • Validation errors that can be detected synchronously (e.g. malformed JSON, failed schema validation, missing scope, auth failure) are still returned as the usual 4xx responses — they will not be reported as a completed-but-failed async operation.

4. Getting the final result

There are two ways to learn when an async operation has finished: polling and webhooks. You can combine them.

4.1 Polling GET /{version}/meta/operations/{operation_key}

GET /v1/meta/operations/e1d8b6c7-be75-c625-ea2d-b10de17aa688
Authorization: Bearer <access_token>           # scope: read

API reference: https://docs.oysterhr.com/reference/get_v1-meta-operations-operation-key

The read scope is required. You can only look up operations that were created by your own OAuth application.

The important fields for polling are:

  • meta.completed — true once the background work has finished (succeeded or failed).
  • meta.success — true on success, false on failure, absent while still in progress.
  • data — the business payload produced by the operation (populated on success).
  • errors — present on failure. Each entry contains at least message.

While the operation is still running, meta.completed is false and meta.success is absent.

Recommended polling strategy

  • Poll no more frequently than once every 2–5 seconds per operation.
  • Use exponential backoff with a cap (e.g. 2s, 4s, 8s, max 30s).
  • Set a sensible overall timeout; if you still see completed: false after your timeout, treat it as "unknown" and contact support with the operation key — do not retry the original write request, as it may still complete.
  • For high‑throughput integrations, prefer webhooks (see below) and poll only as a fallback.

4.2 Webhook: async_operation_completed

If your application has a webhook endpoint, Oyster will POST to it as soon as the operation completes. This removes the need to poll.

Refer to the async_operation_completed webhook API reference for the formal schema.

Notes:

  • data.key is the same operation key that was returned in the original 202 response's meta.operationKey and X-Oyster-Operation-Key header. Use it to correlate the webhook back to the original request.
  • result is present only when success is true, and mirrors the data field you'd see when polling.
  • errors is present only when success is false.
  • Respond with 2xx to acknowledge receipt; non‑2xx responses will be retried per the standard Oyster webhook delivery policy.

5. Recommended integration flow

  1. Call an async endpoint (e.g. POST /v1/hiring/employments/draft).
  2. Read meta.operationKey from the 202 response, or equivalently the X-Oyster-Operation-Key header.
  3. Persist the operation key alongside whatever local record triggered the call (e.g. the internal ID of the draft you intended to create).
  4. Wait for completion:
    • Preferred: receive an async_operation_completed webhook and look up your local record by data.key.
    • Fallback / alternative: poll GET /{version}/meta/operations/{operation_key} until meta.completed is true.
  5. Branch on meta.success (polling) or data.success (webhook):
    • true — use data / data.result as the outcome.
    • false — handle the entries in errors.

6. Error model

Errors follow the standard Oyster REST API errors schema.

Important distinctions:

  • Pre‑acceptance errors (auth, schema validation, missing scope, etc.) are returned as the usual 4xx responses to the original request. They do not produce a 202 and do not emit async_operation_completed.
  • Post‑acceptance errors (failures during background processing) are reported via polling (meta.success: false) and via the async_operation_completed webhook (data.success: false).

7. Idempotency, retries, and tracing

  • Do not blindly retry a 202 request. Once Oyster has accepted the operation, retrying the same call will typically create a second operation. If you are unsure of the outcome, poll the original operationKey first.
  • Every response includes X-Oyster-Operation-Key. Include this value in your logs and in any support requests — it uniquely identifies the request on Oyster's side.
  • The operationKey is a UUID7 and sorts chronologically by creation time, which makes it convenient for log correlation.

8. Quick reference

ConceptWhere to find it
Operation key (every response)X-Oyster-Operation-Key response header
Async acknowledgement bodymeta.operationKey in a 202 response
Poll for resultGET /v1/meta/operations/{operation_key} (scope: read)
Push notificationWebhook event async_operation_completed
Completion flagmeta.completed (poll) / always fired on completion (webhook)
Success flagmeta.success (poll) / data.success (webhook)
Success payloaddata (poll) / data.result (webhook)
Failure payloaderrors (poll) / data.errors (webhook)