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.
| Group | Content types | Request body lands in |
|---|---|---|
| Structured | application/json, multipart/form-data | spread flat into doc |
| Raw | application/xml, text/xml, application/soap+xml, text/plain | doc.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.
<user><name>John</name><age>30</age></user>
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
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.
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.
<UserRequest>
<firstName>John</firstName>
<lastName>Doe</lastName>
</UserRequest>
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: ""
<UserResponse><firstName>John</firstName><lastName>Doe</lastName></UserResponse>
Validation
The request and response validation steps pick a schema in this order:
- The schema defined on the API operation — structured media types only.
- The
defaultSchemaconfigured 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
SOAPActionHTTP header. - SOAP 1.2 sends it as an
actionparameter onContent-Type, for exampleapplication/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
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
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