What the batch protocol does

Batch Protocol lets you send several Workday REST calls in one HTTP request: POST https://{tenantHostname}/batch/v1/{tenantName} with a batch[] array of subrequests. Workday runs them in the order you list them, as a single transaction by default, so if one fails everything already done in the batch is rolled back.

It exists for the multi-call operations Workday's REST design creates. A Change Job through REST is at least three calls (start the event, patch its subresources, submit it), and a prospect with experiences and skills is one create plus one call per child record. Without a batch, a failure halfway through leaves an initiated event or a half-built record in the tenant. With a batch, it leaves nothing.

The batch endpoint has its own version, v1, separate from the versions of the services you call inside it.

Request shape

Each item in batch[] is one subrequest:

{
  "batch": [
    {
      "name": "startChangeJob",
      "method": "POST",
      "relativeUri": "staffing/v6/workers/Employee_ID=21001/jobChanges",
      "body": { "date": "2026-12-01", "reason": { "id": "<reason WID>" } }
    },
    {
      "name": "startDetails",
      "method": "PATCH",
      "relativeUri": "staffing/v6/jobChanges/{startChangeJob:$.id}/startDetails/{startChangeJob:$.id}",
      "body": { "date": "2026-12-01", "reason": { "id": "<reason WID>" } }
    },
    {
      "name": "changeLocation",
      "method": "PATCH",
      "relativeUri": "staffing/v6/jobChanges/{startChangeJob:$.id}/location/{startChangeJob:$.id}",
      "body": { "location": { "id": "<location WID>" } }
    },
    {
      "name": "submitChangeJob",
      "method": "POST",
      "relativeUri": "staffing/v6/jobChanges/{startChangeJob:$.id}/submit",
      "body": {}
    }
  ]
}

The fields Workday defines:

  • name: a unique string of letters, digits, underscores and dashes. Required if anything refers to this subrequest, and Workday strongly recommends it on every subrequest because it is how you find the result in the response. Duplicate names make the whole batch return 400.
  • method: GET, POST, PUT, PATCH or DELETE.
  • relativeUri: starts with the service name and version, then the resource path. Leave out the tenant; it comes from the batch URL.
  • body: the subrequest's JSON body. If the endpoint needs an empty body, as submit does, send "body": {} rather than leaving it out.
  • continueOnFailure: optional, default false.
  • defaultVars: optional default values for references, covered below.

Two relativeUri rules catch people out. Special characters and spaces must be URL-encoded, so a WQL query in a subrequest needs %20 for every space. And bulk=true is not supported in a subrequest.

Passing IDs from one subrequest to the next

A later subrequest can use a value from an earlier response with a referential dependency:

{subrequestName:$.jsonPath|defaultValue}

$.id is the usual target: create something, then use its Workday ID in the next call's path. In the example above, which follows Workday's own Change Job sample, {startChangeJob:$.id} puts the new job change event's ID into both PATCHes and the submit.

Where you can use them:

  • In relativeUri: in a resource or subresource ID (one reference only, nothing else in that segment), in a reference ID value such as COMPANY_ID=Company_{create:$.companyId}, and in query parameter values, where you can mix text and several references.
  • In body field values, always inside double quotes whatever the type. Workday resolves the type, so "age": "{getEmployee:$.age}" becomes the number 27. Field names cannot contain references, and references cannot be nested.

If the earlier response is a collection, the path has to index into it: {getWorkers:$.data[0].id}. Be careful with JSONPath that can return several matches (.., ?() filters, multiple indices). Workday always returns a list for those, even for one match or none, so a reference inside a string becomes "The CEO is [\"Name\"]" and a typo in a filter quietly becomes [] rather than an error.

Default values and defaultVars

After the | you can give a fallback for when the JSONPath finds nothing. A literal default cannot contain {} or [], so for an object or list default, declare it in the same subrequest's defaultVars and reference it with $:

"defaultVars": { "defaultCountry": { "id": "<country WID>" } },
"body": { "country": "{getProspect:$.candidate.name.country|$defaultCountry}" }

The variable has to be the type the field expects: a list where a list goes, an object where an object goes.

Transactions, continueOnFailure and the 25-second limit

With every subrequest at the default continueOnFailure: false, a failure anywhere rolls back all updates in the batch, stops processing and returns a non-200 status with the error. This is the setting for anything that must be all-or-nothing, such as the three Change Job calls.

Set continueOnFailure: true on a subrequest when the rest of the batch should go ahead without it, for example an optional skill on a prospect. If it fails, earlier subrequests are kept, processing continues, and the error is reported in that subrequest's entry in the response.

Three things override continueOnFailure:

  • Timeout. A batch has 25 seconds. If it runs longer, Workday rolls back the whole batch regardless of any flag. If a batch comes near that limit, split it into smaller transactions rather than retrying the same batch.
  • Parse errors. An invalid subrequest field name or a missing required field fails the batch immediately.
  • References to an optional subrequest. A referential dependency cannot point at a subrequest that has continueOnFailure: true. Unless the reference has a default, the referring subrequest errors; with false on the referrer, that is a 400 for the batch.

Note that an invalid service, version or resource in relativeUri on a continueOnFailure: true subrequest does not stop the batch; Workday reports it in the response and moves on. A typo in an optional subrequest can therefore go unnoticed unless you read every status.

Limits and what a batch cannot change

From Workday's limits for Batch Protocol:

  • 375 subrequests per batch at most, whatever the methods.
  • 25 seconds per batch.
  • One authentication for the whole batch. Every subrequest runs as the same account, so that account's security decides what every call can see and do; you cannot authorize subrequests differently.
  • One set of HTTP headers, applied to every subrequest.
  • Attachments can be uploaded but not retrieved through a batch.

Security is still checked per call: each REST endpoint inside the batch needs its own domain or business process permission, as listed for that endpoint in the REST API Explorer.

Reading the response

A batch that ran returns HTTP 200 with total (how many subrequests were processed) and data[], one entry per subrequest with name, status and body. A 200 does not mean everything succeeded. With continueOnFailure, a failed subrequest shows its own status and an error body in its entry:

{
  "name": "create-prospect-experience",
  "body": {
    "error": "invalid request: validation errors",
    "errors": [ { "error": "Enter a start year.", "code": "A258" } ]
  },
  "status": 400
}

So check status on every entry, not just the outer code. The outer codes Workday documents are 400 for an invalid batch (wrong method on the batch endpoint, duplicate names, a failing subrequest without continueOnFailure, a reference to an optional subrequest) and 404 for an incorrect subrequest method or resource, plus 408, 429 and 500.

Prove the batch did what you meant

The batch response tells you what each call returned, not what the tenant now holds. For a transactional batch, read the result back with a separate GET: the worker's job details after the Change Job, or the prospect with its experiences. For a Change Job, also check the submit entry's businessProcessParameters and status, because a submitted event can be In Progress awaiting approval rather than complete. When a batch failed and rolled back, confirm nothing was left behind, for instance that no Change Job event for that worker sits initiated.

For how the ID from a POST comes back and how to use it outside a batch, see getting the instance ID back after a POST. For whether a change is better done through SOAP, where one request can carry more of it, see REST vs SOAP coverage gaps in Workday and changing job profile and compensation in one Change Job SOAP request.

Sources checked