Skip to main content
Version: ACE 5

XML and Raw Content Types in APIs

Overview​

Dynamic APIs can accept and return XML and plain text, not just JSON. This makes it possible to build REST APIs with XML payloads and to use ACE as a SOAP mock service.

Supported content types​

ACE splits content types into two groups. The group decides where the request body lands in doc.

GroupContent typesRequest body lands in
Structuredapplication/json, multipart/form-dataspread flat into doc
Rawapplication/xml, text/xml, application/soap+xml, text/plaindoc.aceRequestBody

Any application/*+xml subtype (for example application/atom+xml) is also treated as raw.

Configuring the API operation​

In the API operation form, pick the Media type for the request body and for each response. The selected media type appears in the published OpenAPI document.

  • Request body: application/json (default), multipart/form-data, application/xml, text/xml, application/soap+xml, text/plain
  • Response: application/json (default), application/xml, text/xml, application/soap+xml, text/plain

Reading a raw request body​

A raw body is passed to the flow verbatim as a string in doc.aceRequestBody. ACE does not convert it.

Request with Content-Type: application/xml
<user><name>John</name><age>30</age></user>
Available in the flow as
doc.aceRequestBody; // "<user><name>John</name><age>30</age></user>"

JSON and multipart/form-data requests are unaffected — their bodies still spread flat into doc as before.

To work with the XML, convert it with the XML ⇄ JSON step:

- name: Parse XML request
stepType: xml
config:
mode: XML to JSON
attrKey: $
explicitArray: false
xml: "{{aceRequestBody}}"
targetPath: userData
info

aceRequestBody is a reserved key. A query parameter, path parameter, or JSON field with this name is stripped from incoming requests, so it can never collide with the raw body. Request headers are never merged into doc — they stay in $headers — so they cannot collide either.

Returning a raw response​

ACE does not convert the response body either — the flow must produce it. Build the XML with the XML ⇄ JSON step, then declare the content type with the Response step's new Content type field.

note

An explicit Content-Type set in the Response step's Response headers takes precedence over the Content type field.

Example: XML in, XML out​

Request and response media types both set to application/xml.

Request
<UserRequest>
<firstName>John</firstName>
<lastName>Doe</lastName>
</UserRequest>
Flow: process-xml-user
tags: []
sampleInputSchema: ""
sampleData: {}
description: ""
baseFlow: null
steps:
- name: Parse XML request
description: ""
config:
mode: XML to JSON
attrKey: $
explicitArray: false
xml: "{{aceRequestBody}}"
targetPath: userData
stepType: xml
condition: ""
- name: Build XML response
description: ""
config:
mode: JSON to XML
attrKey: $
targetPath: result
json:
UserResponse:
firstName: "{{userData.UserRequest.firstName}}"
lastName: "{{userData.UserRequest.lastName}}"
stepType: xml
condition: ""
- name: Set response content type
description: ""
config:
contentType: application/xml
stepType: response
condition: ""
Response (Content-Type: application/xml)
<UserResponse><firstName>John</firstName><lastName>Doe</lastName></UserResponse>

Validation​

The request and response validation steps pick a schema in this order:

  1. The schema defined on the API operation — structured media types only.
  2. The defaultSchema configured on the validation step itself.

For a raw media type, option 1 never applies: its schema is published in the OpenAPI document as documentation, but the runtime does not enforce it. So a raw body is validated only if you set defaultSchema on the step; otherwise the body is skipped. Only the body is affected — query, path and header parameters declared on the operation are still validated as usual.

To validate the content of a raw body, convert it to JSON with the XML ⇄ JSON step first, then validate that.

SOAP mock services​

ACE can serve as a SOAP mock endpoint. Use text/xml for SOAP 1.1 and application/soap+xml for SOAP 1.2. Either way the SOAP envelope arrives in doc.aceRequestBody.

The two versions differ only in where the operation name is:

  • SOAP 1.1 sends it in the SOAPAction HTTP header.
  • SOAP 1.2 sends it as an action parameter on Content-Type, for example application/soap+xml; action="GetUser". Extra parameters like this do not affect the content type ACE resolves — application/soap+xml; action="GetUser" is still raw.

There is no dedicated SOAP dispatcher. To serve several SOAP operations from one endpoint, use a start handler that sets the reserved aceApiFlow property — see API handlers for how changing the API flow works.

SOAP 1.1 dispatcher​

Handler: soap11-dispatcher
steps:
- name: Route SOAPAction
stepType: code-ivm
config:
codeBlock: |
const flows = {
'GetUser': 'get-user-flow',
'CreateUser': 'create-user-flow',
};
const action = ($header('SOAPAction') || '').replace(/"/g, '');
return flows[action];
targetPath: aceApiFlow

SOAP 1.2 dispatcher​

Handler: soap12-dispatcher
steps:
- name: Route SOAP 1.2 action
stepType: code-ivm
config:
codeBlock: |
const flows = {
'GetUser': 'get-user-flow',
'CreateUser': 'create-user-flow',
};
const contentType = $header('Content-Type') || '';
const match = contentType.match(/action\s*=\s*"?([^";]+)"?/i);
return match ? flows[match[1]] : undefined;
targetPath: aceApiFlow