REST vs SOAP: Which One and Why?
A strict XML protocol with a contract vs the flexible style most APIs use today, with a live call to each from the terminal.
If you've only built modern APIs, SOAP can feel like a fossil. But you'll still meet it in banking, government, shipping and older enterprise systems, sometimes in the same project as a shiny REST API. Here's how the two differ, with a real call to each that you can run in a minute.
TL;DR
- REST is an architectural style: resources at URLs, HTTP methods (
GET,POST,PUT,DELETE) and status codes, usually with JSON. - SOAP is a protocol: every message is an XML envelope, usually sent as an HTTP
POST, and errors come back as asoap:Faultin the XML. - SOAP services usually publish a WSDL, a machine-readable contract your tools can turn into ready-made methods.
- Build new APIs as REST. Use SOAP when the system you integrate with only offers SOAP.
- In PHP,
SoapClientreads the WSDL for you, but it needs thesoapextension, which isn't always enabled.
The problem
Developers who grew up on JSON APIs often hit SOAP for the first time on an integration with an older system, and it goes badly in a few predictable ways:
- Building the XML by hand with string concatenation, then losing an afternoon to a missing namespace.
- Checking only the HTTP status. A SOAP error is an XML
soap:Faultin the body, so code that only checks the status can treat a failed call as a success. - Writing
new SoapClient(...)on a server where PHP'ssoapextension isn't installed and gettingClass "SoapClient" not found.
Why it happened
The two look alike because both usually travel over HTTP, but they come from different ideas.
SOAP is like sending a formal letter through a government office. There's a strict envelope format and a rule book (the WSDL) that says exactly which forms exist and which fields go on each. Everything goes through the same counter (one URL, always POST), and a rejected form comes back as an official rejection letter (a soap:Fault).
REST is like a shop with labelled shelves. Each thing has its own address (/orders/1001), and the HTTP method says what you want to do with it. There are habits everyone follows, but no single rule book.
| REST | SOAP | |
|---|---|---|
| What it is | An architectural style | A protocol with a formal spec |
| Message format | Usually JSON (XML and others work too) | Always XML |
| Contract | Optional (OpenAPI is common) | Usually a WSDL |
| Transport | HTTP in practice | Usually HTTP, but the spec allows others such as SMTP |
| Endpoints | One URL per resource, many methods | Often one URL, always POST over HTTP |
| Errors | HTTP status codes (404, 422) |
A soap:Fault element in the XML, usually with HTTP 500 |
| Extra standards | Pick your own (OAuth, JSON Schema) | Optional WS-* specs such as WS-Security |
| Weight | Light | Heavier messages and tooling |
A note on that last-but-one row: WS-Security and the other WS-* specs aren't part of SOAP itself. They're separate standards that SOAP services can add, and many don't.
How to choose
- New API you control: REST. Browsers, mobile apps and every language handle JSON over HTTP with no extra tooling.
- The other side only offers SOAP: use SOAP, and let a library build the envelopes from the WSDL instead of writing XML by hand.
- The other side offers both: pick REST unless you need something only the SOAP API has.
SOAP is also shrinking in places it used to be the default. Salesforce, for example, no longer supports its SOAP login() call from API version 65.0 onward and plans to retire it in Summer '27. The SOAP API stays, but you sign in with OAuth instead of a username and password.
Steps
These need curl and jq, plus Docker for the PHP step. They call two public APIs, so you need internet access.
Make a REST call.
curl -s https://api.github.com/repos/curl/curl | jq '{full_name, stargazers_count}'It worked if you get a small JSON object with
"full_name": "curl/curl"and a star count. One URL names one resource,GETreads it, and the answer is plain JSON.Make a SOAP call. This uses a public number-conversion service:
curl -s https://www.dataaccess.com/webservicesserver/NumberConversion.wso \ -H 'Content-Type: text/xml; charset=utf-8' \ -d '<?xml version="1.0" encoding="utf-8"?> <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body> <NumberToWords xmlns="http://www.dataaccess.com/webservicesserver/"> <ubiNum>42</ubiNum> </NumberToWords> </soap:Body> </soap:Envelope>'It worked if you get an XML envelope back containing
<m:NumberToWordsResult>forty two </m:NumberToWordsResult>. Notice it's aPOSTto one URL, and the operation name (NumberToWords) is inside the XML, not in the URL. Many services also need aSOAPActionheader; the WSDL tells you its value.Look at the contract.
curl -s 'https://www.dataaccess.com/webservicesserver/NumberConversion.wso?WSDL' | grep -o 'operation name="[A-Za-z]*"' | sort -uIt worked if you see
operation name="NumberToDollars"andoperation name="NumberToWords". That's the WSDL listing every operation the service offers, which is what tools use to generate code.Let PHP read the WSDL for you. Save this as
soap.php:<?php $client = new SoapClient('https://www.dataaccess.com/webservicesserver/NumberConversion.wso?WSDL'); // Methods and types come from the WSDL print_r($client->__getFunctions()); $result = $client->NumberToWords(['ubiNum' => 42]); echo $result->NumberToWordsResult, PHP_EOL;The official PHP Docker image doesn't include the
soapextension, which is a good reminder to check your own servers. Install it and run the script:docker run --rm -v "$PWD":/w php:8.4-cli sh -c \ 'apt-get update -qq && apt-get install -y -qq libxml2-dev && docker-php-ext-install soap && php /w/soap.php'It worked if the last lines show the service's functions and then
forty two. Each function is listed twice because the WSDL describes both SOAP 1.1 and SOAP 1.2 versions. No XML was written by hand:SoapClientbuilt the envelope from the WSDL. On a normal server, runphp -m | grep soapto check the extension is there.
How it works
A REST call names the resource in the URL and uses the HTTP method and status code:
sequenceDiagram
participant App as Your app
participant API as REST API
App->>API: GET /repos/curl/curl
API-->>App: 200 OK + JSON
App->>API: GET /repos/nope/nope
API-->>App: 404 Not Found + JSON error
A SOAP call reads the contract first, then posts XML envelopes to one URL:
sequenceDiagram
participant App as Your app (SoapClient)
participant Svc as SOAP service
App->>Svc: GET ?WSDL
Svc-->>App: WSDL (operations and types)
App->>Svc: POST envelope: NumberToWords(42)
Svc-->>App: 200 OK, envelope: "forty two"
Note over App,Svc: on error the body holds a soap:Fault,<br/>usually with HTTP 500
How to avoid it next time
- Never build SOAP XML by hand in production code. Use a client that reads the WSDL (
SoapClientin PHP,zeepin Python,wsimport-generated classes in Java). - Save a copy of the WSDL in your repo. If the service changes it, a diff shows you what changed.
- Check for a
soap:Faultin every response, not only the HTTP status. - Log the raw request and response XML while you build the integration. Most SOAP errors are a wrong namespace or a missing field, and the XML shows it at once.
- Check the PHP extension list on every server (
php -m | grep soap) before you deploy SOAP code.
Tools used
- curl: sends the REST and SOAP requests
- jq: picks fields out of the JSON response
- GitHub REST API: the public REST API called in step 1
- DataAccess NumberConversion: the public SOAP service called in steps 2 to 4
- PHP SoapClient: builds SOAP calls from a WSDL
- Docker: runs PHP with the
soapextension without installing anything - Salesforce SOAP API: an example of a big platform that still offers SOAP
Comments
Comments and votes open again when the site is back online.