> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://help.moveworks.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://help.moveworks.com/_mcp/server.

# Dynamic XML and SOAP Request Bodies

Some XML and SOAP APIs accept a large superset of optional elements and decide which ones are required at runtime. Workday Web Services (WWS) operations such as `Change_Job_Request` and `Submit_Payment_Election_Enrollment` work this way: nearly every field is optional in the schema, and the tenant enforces per-country requirements server-side. This guide shows how to send a runtime-selected subset of XML elements from **one** HTTP Action instead of building a separate action for every variant.

For conditional JSON bodies and query parameters, see [Optional Query Parameters in HTTP Actions](/agent-studio/actions/http-actions/optional-query-parameters-in-http-actions). For parsing XML *responses*, see [Event streaming (SSE) and XML responses](/agent-studio/actions/http-actions/http-action-event-streaming-and-xml).

# Key concept: XML bodies are strings

Data Mapper and `EVAL()` produce JSON. Nothing converts a mapped object into XML for you. To call an XML or SOAP endpoint, you build the request body as a **string** and send it with the right headers:

* `Content-Type: text/xml` (or the content type your API expects)
* `SOAPAction` header, if the SOAP service requires it

XML responses are converted to JSON automatically at the HTTP Action layer, so you do not need to parse the response yourself.

# Choose a technique

| Technique                                                                   | Best for                                                                    | Where the logic lives         |
| --------------------------------------------------------------------------- | --------------------------------------------------------------------------- | ----------------------------- |
| [Mustache conditional sections](#technique-1-mustache-conditional-sections) | Dropping individual elements when a slot or variable is empty               | HTTP Action **Body**          |
| [`RENDER()` template](#technique-2-render-template)                         | A fixed envelope with values computed by DSL (dates, formatting)            | Data Mapper (input arguments) |
| [Script Action](#technique-3-build-the-payload-in-a-script-action)          | Loops over runtime-fetched field lists, merging existing data, XML escaping | Compound Action `script` step |

You can combine them. For example, a Script Action can build a fragment that a Mustache template inserts into the envelope.

# Technique 1: Mustache conditional sections

The HTTP Action **Body** uses the [Mustache templating language](https://mustache.github.io/). A section tag (`{{#variable}}...{{/variable}}`) renders its content only when the variable has a value. Wrap each optional element in a section so that the entire element disappears when the variable is empty, null, or not provided.

Template the full superset of fields once. Elements whose variables are not populated do not render.

#### Abbreviated examples

The Workday envelopes on this page are shortened to show the pattern. Use the element names and structure from your tenant's WWS schema when building a real request.

```xml
<?xml version="1.0" encoding="UTF-8"?>
<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
  <env:Body>
    <wd:Submit_Payment_Election_Enrollment_Request xmlns:wd="urn:com.workday/bsvc" wd:version="v46.1">
      <wd:Payment_Election_Enrollment_Data>
        <wd:Worker_Reference>
          <wd:ID wd:type="Employee_ID">{{{employee_id}}}</wd:ID>
        </wd:Worker_Reference>
        {{#iban}}<wd:IBAN>{{{iban}}}</wd:IBAN>{{/iban}}
        {{#routing}}<wd:Routing_Transit_or_Institution_Number>{{{routing}}}</wd:Routing_Transit_or_Institution_Number>{{/routing}}
        {{#account_number}}<wd:Bank_Account_Number>{{{account_number}}}</wd:Bank_Account_Number>{{/account_number}}
      </wd:Payment_Election_Enrollment_Data>
    </wd:Submit_Payment_Election_Enrollment_Request>
  </env:Body>
</env:Envelope>
```

If `iban` is set and `routing` is empty, the request contains an `<wd:IBAN>` element and no `<wd:Routing_Transit_or_Institution_Number>` element.

Guidelines:

* **Use triple braces** (`{{{variable}}}`) for the values. Double braces HTML-escape the variable, which can corrupt values that contain characters like `&`. See [Variable Escaping](/agent-studio/actions/http-actions#variable-escaping).
* **Conditional variables must be scalars** (string, number, or boolean). If a section variable holds an object or list, Mustache iterates over it instead of treating it as a conditional. To gate an element on an object or list, add a separate boolean variable and use that as the section variable.
* Set `Content-Type: text/xml` (and `SOAPAction`, if required) in the **Headers** table.

# Technique 2: RENDER() template

`RENDER()` in the Data Mapper substitutes `{{variable}}` placeholders in a template string with values from `args`. Because the `args` are Data Mapper expressions, you can compute values with DSL, such as formatting today's date, before they are inserted into the envelope. Map the result to the input argument your HTTP Action uses as its body.

The following example builds a Workday `Change_Job_Request` that updates a contingent worker's contract end date. `effective_date` is computed at runtime with `$FORMAT_TIME`, while `contractor_employee_id` and `new_contract_end_date` come from slots.

```yaml
RENDER():
  args:
    contractor_employee_id: contractor_employee_id
    effective_date: $FORMAT_TIME($TIME(), "%Y-%m-%d", "US/Eastern")
    new_contract_end_date: new_contract_end_date
  template: |-
    <?xml version="1.0" encoding="UTF-8"?> <env:Envelope
        xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema">
        <env:Body>
            <wd:Change_Job_Request xmlns:wd="urn:com.workday/bsvc" wd:version="v46.1">
                <wd:Business_Process_Parameters>
                    <wd:Auto_Complete>true</wd:Auto_Complete>
                    <wd:Run_Now>true</wd:Run_Now>
                    <wd:Discard_On_Exit_Validation_Error>true</wd:Discard_On_Exit_Validation_Error>
                    <wd:Comment_Data>
                        <wd:Comment>Submitted by Moveworks</wd:Comment>
                    </wd:Comment_Data>
                </wd:Business_Process_Parameters>
                <wd:Change_Job_Data>
                    <wd:Worker_Reference>
                        <wd:ID wd:type="Contingent_Worker_ID">{{contractor_employee_id}}</wd:ID>
                    </wd:Worker_Reference>
                    <wd:Effective_Date>{{effective_date}}</wd:Effective_Date>
                    <wd:Change_Job_Detail_Data>
                        <wd:Reason_Reference>
                            <wd:ID wd:type="Change_Job_Subcategory_ID">CHANGE_JOB_SUBCATEGORY-3-12</wd:ID>
                        </wd:Reason_Reference>
                        <wd:Contract_End_Date>{{new_contract_end_date}}</wd:Contract_End_Date>
                    </wd:Change_Job_Detail_Data>
                </wd:Change_Job_Data>
            </wd:Change_Job_Request>
        </env:Body>
    </env:Envelope>
```

To build list-shaped XML (a repeated element for every item in an array), use `$CONCAT` and `$MAP` instead. See [Examples: Sending an XML Request](/agent-studio/configuration-languages/mapper/examples-sending-an-xml-request).

# Technique 3: Build the payload in a Script Action

When the set of fields is itself data (for example, a list of field names fetched from an earlier action), or when you need to merge existing values or escape user input, build the envelope in a [Script Action](/agent-studio/actions/script-actions) inside a Compound Action. The script's last expression becomes its `output_key`, and the HTTP Action body is simply `{{{payload_xml}}}`.

The example below loops over a field map, skips empty values, and escapes user-entered text without importing any libraries, so it runs in APIthon.

```yaml
steps:
  - script:
      output_key: payload_xml
      input_args:
        employee_id: data.employee_id
        fields: data.collected_fields
      code: |
        def esc(value):
            return str(value).replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")

        elements = ""
        for tag, value in fields.items():
            if value is None or value == "":
                continue
            elements += "<wd:" + tag + ">" + esc(value) + "</wd:" + tag + ">"

        (
            '<?xml version="1.0" encoding="UTF-8"?>'
            '<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">'
            '<env:Body>'
            '<wd:Submit_Payment_Election_Enrollment_Request xmlns:wd="urn:com.workday/bsvc" wd:version="v46.1">'
            '<wd:Payment_Election_Enrollment_Data>'
            '<wd:Worker_Reference><wd:ID wd:type="Employee_ID">' + esc(employee_id) + '</wd:ID></wd:Worker_Reference>'
            + elements +
            '</wd:Payment_Election_Enrollment_Data>'
            '</wd:Submit_Payment_Election_Enrollment_Request>'
            '</env:Body>'
            '</env:Envelope>'
        )
  - action:
      action_name: workday_submit_payment_election
      output_key: submit_result
      input_args:
        payload_xml: data.payload_xml
```

Here `data.collected_fields` is an object such as `{"IBAN": "DE89...", "Bank_Account_Number": ""}`. Only keys with a value become elements.

Keep these constraints in mind:

* **Latency**: each Script Action adds latency to the plugin. Use Techniques 1 or 2 when the logic is simple.
* **Size limits**: APIthon code is limited to 4096 bytes and strings to 4096 characters. A long envelope, especially one that merges an existing record for a full-replace submit, can exceed the string limit. Keep the static parts of the envelope in the HTTP Action **Body** and generate only the variable fragment in the script. See [APIthon: A Special Kind of Python](/agent-studio/actions/script-actions/apithon-a-special-kind-of-python).
* **Imports**: APIthon does not allow imports, so escape user-entered values with a `replace` chain like `esc()` above. If the Python language option is enabled in your Script Action editor, you can use the standard library instead, as shown in [Examples: Sending an XML Request](/agent-studio/configuration-languages/mapper/examples-sending-an-xml-request).

# Supporting patterns

## Fetch which fields apply at runtime

The set of required fields often depends on the user. In the Workday example, the required payment election fields vary by country. Rather than encoding those rules in the plugin:

1. Add an earlier action that returns the applicable fields for the current user, for example a Workday RaaS report keyed by the user's country.
2. Collect the values. Slot definitions are static, so use an `object` slot and let the assistant guide the user through the fields returned in step 1. See [Slots](/agent-studio/conversation-process/slots) and [Resolver Strategies](/agent-studio/conversation-process/resolver-strategies).
3. Pass the collected map into any of the three techniques above.

## Strip null fields from a JSON superset

For JSON APIs, the equivalent of Technique 1 is `EVAL()` with `$FILTER` to remove null or empty keys from a superset object. See [Omit null or empty keys from a payload](/agent-studio/configuration-languages/common-dsl-mapper-patterns#omit-null-or-empty-keys-from-a-payload).

## Let the server validate

If you do not want to maintain field metadata, send the superset payload and relay the server's validation message to the user. Workday, for example, returns human-readable errors such as "X is required for this country". This needs no metadata maintenance, but the user only learns about a missing field after a failed submit.