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
completedand 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/draftPOST /{version}/expenses/expenses
Note: the body of a
202response contains onlymeta.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:
202means "accepted for processing", not "succeeded".- The
operationKeyin the body andX-Oyster-Operation-Keyheader 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 /{version}/meta/operations/{operation_key}GET /v1/meta/operations/e1d8b6c7-be75-c625-ea2d-b10de17aa688
Authorization: Bearer <access_token> # scope: readAPI 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—trueonce the background work has finished (succeeded or failed).meta.success—trueon success,falseon 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 leastmessage.
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: falseafter 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
async_operation_completedIf 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.keyis the same operation key that was returned in the original202response'smeta.operationKeyandX-Oyster-Operation-Keyheader. Use it to correlate the webhook back to the original request.resultis present only whensuccessistrue, and mirrors thedatafield you'd see when polling.errorsis present only whensuccessisfalse.- Respond with
2xxto acknowledge receipt; non‑2xx responses will be retried per the standard Oyster webhook delivery policy.
5. Recommended integration flow
- Call an async endpoint (e.g.
POST /v1/hiring/employments/draft). - Read
meta.operationKeyfrom the202response, or equivalently theX-Oyster-Operation-Keyheader. - Persist the operation key alongside whatever local record triggered the call (e.g. the internal ID of the draft you intended to create).
- Wait for completion:
- Preferred: receive an
async_operation_completedwebhook and look up your local record bydata.key. - Fallback / alternative: poll
GET /{version}/meta/operations/{operation_key}untilmeta.completedistrue.
- Preferred: receive an
- Branch on
meta.success(polling) ordata.success(webhook):true— usedata/data.resultas the outcome.false— handle the entries inerrors.
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
202and do not emitasync_operation_completed. - Post‑acceptance errors (failures during background processing) are reported via polling (
meta.success: false) and via theasync_operation_completedwebhook (data.success: false).
7. Idempotency, retries, and tracing
- Do not blindly retry a
202request. 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 originaloperationKeyfirst. - 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
operationKeyis a UUID7 and sorts chronologically by creation time, which makes it convenient for log correlation.
8. Quick reference
| Concept | Where to find it |
|---|---|
| Operation key (every response) | X-Oyster-Operation-Key response header |
| Async acknowledgement body | meta.operationKey in a 202 response |
| Poll for result | GET /v1/meta/operations/{operation_key} (scope: read) |
| Push notification | Webhook event async_operation_completed |
| Completion flag | meta.completed (poll) / always fired on completion (webhook) |
| Success flag | meta.success (poll) / data.success (webhook) |
| Success payload | data (poll) / data.result (webhook) |
| Failure payload | errors (poll) / data.errors (webhook) |
Updated about 2 hours ago