Self-hosted from my home lab. It's offline right now, so you're viewing a snapshot. Check back soon for the live site.
All posts
Tech Basics

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 a soap:Fault in 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, SoapClient reads the WSDL for you, but it needs the soap extension, 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:Fault in 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's soap extension isn't installed and getting Class "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.

  1. 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, GET reads it, and the answer is plain JSON.

  2. 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 a POST to one URL, and the operation name (NumberToWords) is inside the XML, not in the URL. Many services also need a SOAPAction header; the WSDL tells you its value.

  3. Look at the contract.

    curl -s 'https://www.dataaccess.com/webservicesserver/NumberConversion.wso?WSDL' | grep -o 'operation name="[A-Za-z]*"' | sort -u
    

    It worked if you see operation name="NumberToDollars" and operation name="NumberToWords". That's the WSDL listing every operation the service offers, which is what tools use to generate code.

  4. 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 soap extension, 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: SoapClient built the envelope from the WSDL. On a normal server, run php -m | grep soap to 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 (SoapClient in PHP, zeep in 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:Fault in 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

Was this useful?
#api#basics#rest#soap

Comments

Comments and votes open again when the site is back online.