WSDL documents describing SOAP based Web Services can be converted into OpenAPI specifications describing APIs with JSON messages. The WSDL and the included XML Schemas (XSD) provide all the data for the transformation.
Membrane API Gateway provides a converter that automatically translates WSDL operations and XML Schema types into OpenAPI operations and JSON Schemas.

The OpenAPI specification can be used to generate client code and server implementations. It also provides the mappings required by Membrane's SOAP to REST gateway to transform between JSON and SOAP/XML.
WSDL operations can also be mapped to HTTP methods and resource paths to expose the existing SOAP Web Service as a RESTful API.
The snippet shows a WSDL portType with five operations:
<wsdl:portType name="PartnerPT">
<wsdl:operation name="getPartner">
...
</wsdl:operation>
<wsdl:operation name="getPartners">
...
</wsdl:operation>
<wsdl:operation name="createPartner">
...
</wsdl:operation>
<wsdl:operation name="updatePartner">
...
</wsdl:operation>
<wsdl:operation name="deletePartner">
...
</wsdl:operation>
</wsdl:portType>The converter automatically translates these WSDL operations into OpenAPI endpoints.

By default, the generated API follows the Remote Procedure Call (RPC) style rather than REST principles. The paths represent operations, such as update-partner, instead of resources such as partners, and all endpoints use the POST method. This operation-oriented style is well suited for APIs that represent actions or business processes. If you prefer a resource-oriented RESTful API, the converter also allows you to map WSDL operations to HTTP methods and resource paths.
A WSDL typically describes operations such as:
createPartner(name, age, city)getPartner(id)getPartners()updatePartner(id, name, age, city)deletePartner(id)A resource-oriented REST API provides the same functionality through resources and HTTP methods:
GET|POST /partners
GET|PUT|DELETE /partners/{id}With Membrane, you can define an HTTP method and URI template for each WSDL operation to expose the SOAP Web Service as resources:
api:
port: 2000
name: Partner REST API
flow:
- wsdl2openapi:
wsdl: partner.wsdl
operations:
getPartners:
method: GET
path: /partners
getPartner:
method: GET
path: /partners/{id}
createPartner:
path: /partners
method: POST
updatePartner:
method: PUT
path: /partners/{id}
deletePartner:
method: DELETE
path: /partners/{id}The converter uses these mappings to generate an OpenAPI description with resource-oriented REST endpoints:

Membrane does not automatically decide how WSDL operations should be mapped to REST resources, because choosing meaningful resources, paths, and HTTP methods is an API design decision. However, an AI coding assistant can generate an initial mapping for you.
An AI coding assistant can help you convert a WSDL into a RESTful OpenAPI with Membrane. Follow these steps:
cd membrane-api-gateway-*<WSDL URL or path> with the location of your WSDL.apis.yaml configuration../membrane.sh -c apis.yamlYou're in the Membrane API Gateway distribution directory.
First, study the tutorials in tutorials/soap/, especially
95-WSDL-to-OpenAPI.yaml, 96-WSDL-to-OpenAPI-REST.yaml, and
97-WSDL-XSD-Features.yaml.
Then create an apis.yaml that exposes the SOAP service described by
<WSDL URL or path> as a REST API using wsdl2openapi.
Read the WSDL and map each SOAP operation to a RESTful resource:
- Derive resource paths from the business nouns in the operation names
(GetCustomer -> GET /customers/{id}, CreateOrder -> POST /orders, ...).
- Choose the HTTP method based on the operation's semantics:
read -> GET, create -> POST, full update -> PUT,
partial update -> PATCH, remove -> DELETE.
- Use plural resource names and path parameters for identifiers.
- Group related operations under a common tag.
- Keep the mappings consistent.
- If an operation has no natural REST representation, choose a reasonable
mapping and explain the compromise.
Configure the mappings under the `operations` element of wsdl2openapi,
giving each operation a `method`, `path`, and `tag`.
Configure the SOAP service described by the WSDL as the backend target.
Show me a mapping table with:
SOAP operation -> HTTP method -> REST path
Finally, explain how to start Membrane with the generated configuration
and how to open http://localhost:2000/api-docs to access the generated
OpenAPI documentation.Consider a SOAP operation that retrieves a partner by ID:
getPartner(id)The Web Service expects the id as an XML element in the request:
<s11:Envelope xmlns:s11="http://schemas.xmlsoap.org/soap/envelope/">
<s11:Body>
<ns:getPartnerRequest xmlns:ns="http://example.com/partner">
<id>1</id>
</ns:getPartnerRequest>
</s11:Body>
</s11:Envelope>To expose this operation as a RESTful GET endpoint, map the id element to a path parameter:
getPartner:
method: GET
path: /partners/{id}The WSDL operation is now represented in the generated OpenAPI description as a RESTful resource with an id path parameter:

Sidenote: Actually this isn't a resource but a URI template for resources.
Consider a SOAP get-operation that accepts multiple parameters in the request body:
<s11:Envelope xmlns:s11="http://schemas.xmlsoap.org/soap/envelope/">
<s11:Body>
<ns:getPartnersRequest xmlns:ns="http://example.com/partner">
<city>Paris</city>
<kind>COMPANY</kind>
</ns:getPartnersRequest>
</s11:Body>
</s11:Envelope>The SOAP operation can be mapped to a RESTful GET endpoint:
getPartners:
method: GET
path: /partnersSince a GET request has no request body in the generated API, the converter maps the SOAP input elements to OpenAPI query parameters.

A REST client can now call the operation using a query string:
GET /partners?city=Paris&kind=COMPANYThe converter supports the following XML Schema (XSD) features.
xsd:complexTypexsd:complexType and xsd:simpleTypexsd:simpleType restrictions, resolved to their base primitivetype= attribute and element references by ref=xsd:sequence and xsd:all, including arbitrary nestingxsd:choice. All alternatives become properties, plus a oneOf that requires exactly one of them; optional and repeatable choices are documented in the schema descriptionxsd:group referencesminOccurs="0" → optional property; anything else → requiredmaxOccurs="unbounded" or > 1 → arrayxsd:complexContent / xsd:extensionxsd:complexContent / xsd:restrictionxsd:simpleContent extension and restrictionxsd:attribute mapped to an @-prefixed propertyuse="required"enumeration (typed to the field's own type — numeric, boolean, string)pattern (anchored; multiple patterns combined as alternatives)length, minLength, maxLengthminInclusive, maxInclusive, minExclusive, maxExclusive (OpenAPI 3.1 exclusive-bound keywords)default=fixed=nillable="true" (3.1 "null" type)string, boolean, int/long (with int32/int64), float, double, decimaldate, dateTime, time, duration, anyURI, base64Binary, hexBinary → proper formatnormalizedString, token, language, QName, NOTATION, the gYear/gMonth/gDay/gYearMonth/gMonthDay familyinteger, short, byte, positiveInteger, unsignedLong, …)x-xsd-type extension so tools can recover itxsd:import and xsd:include, resolved across the full import graph (transitively)xsd:documentation carried over as the schema descriptionelement=), document/literal bare (multiple parts), RPC-style parts (type=)The following constructs degrade silently. No exceptions are thrown:
xsd:anyxsd:anyAttributexsd:list A whitespace-separated list type falls through to plain stringxsd:unionxsd:simpleType without a restriction child resolves to stringabstract="true" types and xsi:type at runtimexsd:attributeGroup Attribute groups are not expanded (only direct xsd:attribute children are read); xsd:group for elements is supportedxsd:redefine and xsd:overridetotalDigits, fractionDigits, whiteSpace are ignored by design\p{IsBasicLatin}, character-class subtraction) stays unusable in an ECMA regex enginemixed="true"). The text alongside child elements has no representation.xsd:all is treated identically to xsd:sequence; its "each at most once, any order" rule is not separately enforcedminOccurs="2" or maxOccurs="5" do not become minItems/maxItemsxsd:unique, xsd:key, xsd:keyrefxsd:assert / xsd:alternativenillable on a declaration whose type is a shared component forces the type inline rather than being expressed on the reference (a design choice, not a gap, but it means such types are duplicated)minOccurs="0"), repeatable (maxOccurs>1), or has an all-optional alternative cannot be encoded as oneOf — the constraint is stated in the description and is not enforced by validationIf you need a construct that the converter does not support yet, open an issue at our GitHub repository or send us an email at info@predic8.de.