The task sounds routine: check that an order is returned by its number. The request is built, the button is pressed, the response says 200 — check closed. Yet the body held a <soap:Fault>: there is no order with that number, the service said so honestly, and the status had nothing to do with its words.
Misses like this are not carelessness. An API has a style — an agreement about where the address is, where the action is and where the error is. There are three styles in practice, and the style decides where you send the request, where you look for a field and what counts as a refusal.
The same question in three styles: REST asks at the order's own address and answers with a status, SOAP posts an envelope to one shared address, GraphQL lists the fields it wants. In two cases out of three the refusal arrives inside the body with code 200 — the status only says that the server answered.
REST: the address is a thing, the method is the action
The most common style rests on one idea: every thing has its own address, and what to do with it is said by the method. Order 42 lives at /orders/42: fetch it with GET, create one with POST /orders, remove it with DELETE /orders/42. The action is not written into the address: the address answers «what», the method answers «what to do». The server usually replies in JSON and puts the outcome into the status code: 200 — here it is, 201 — created, 404 — no such order, 400 — the body is not acceptable.
The second property explains flaky defects: the server does not remember the previous request. Who you are is stated in every single request, by the header with the token. So requests can be run one at a time and in any order; if the second one works only right after the first, the server kept something on its side.
«Almost-REST» is recognised by three signs: the action moved into the address (/getOrder?id=42); everything, reading included, goes by POST; and the worst one — 200 with {"error": "order not found"} in the body. The last one changes your work: the status alone is not enough, every check is driven by the body. And if the description promises 4xx codes, the mismatch is raised as a defect.
SOAP: an envelope, one address and refusal inside success
SOAP is older than REST and lives where systems agreed long ago and for a long time: banks, insurers, government services.
The whole message is an XML envelope: Envelope, with a Header for the technical part (signature, credentials) and a Body holding the operation and its parameters. There is one address for the entire service, the method is almost always POST, and what is being done is written inside the envelope. Consequence: in the Network tab the address tells you nothing about what happened — you have to open the body.
The service description is WSDL: an XML file listing operations, field types and the address; it is imported into Postman or SoapUI, and request stubs are generated for you.
What matters for checks is how a refusal arrives: a <soap:Fault> inside the body.
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<soap:Fault>
<faultcode>soap:Client</faultcode>
<faultstring>Order 42 not found</faultstring>
</soap:Fault>
</soap:Body>
</soap:Envelope>
By the specification a fault is supposed to come with code 500, but in practice services answer 200 all the time: a business refusal — «no such order», «account blocked» — is placed straight into the body of a successful envelope. In SOAP you look at the status but decide by the body.
GraphQL: the client orders the fields
REST has the opposite trouble: to draw one screen the application calls three addresses and uses two of the thirty fields it receives. GraphQL flips the order — there is one address (/graphql), and the client lists the fields it needs right in the request body:
query {
order(id: 42) {
total
customer { name }
}
}
The response repeats the shape of the request: data.order.total, data.order.customer.name — exactly what was asked for. Which means «the same request» in two checks differs by the set of fields, and a green first one promises nothing about the second.
Errors live in an errors array next to data, and the status is 200. The answer can also be partial: data arrived, some fields inside it are null, and the reason is in errors.
Beyond field values, three more things are worth checking. A field that is not in the schema: the server must refuse to execute the query at all and return an error, not silently ignore it. Depth: the chain order → customer → their orders loops back, and without a limit the service is taken down by a single request. Field-level rights: a customer's phone number must not reach someone who is not entitled to it, even when the endpoint itself is open to them.
The three styles side by side
A cheat sheet for your first day:
| REST | SOAP | GraphQL | |
|---|---|---|---|
| Address | one per resource | one per service | one: /graphql |
| Action | method GET, POST | operation in envelope | query, mutation |
| Refusal | status 4xx/5xx | <soap:Fault> at 200 | errors at 200 |
| Contract | OpenAPI, Swagger UI | WSDL | service schema |
| Tooling | Postman | Postman, SoapUI | Postman, GraphiQL |
Where the contract lives: OpenAPI and Swagger
«Is the field customerId or clientId?», «does 404 happen here at all?» — such questions are not answered from memory: the answer is in the API description.
For REST the description is written in the OpenAPI format: a YAML or JSON file with addresses, methods, parameters, body schemas, required fields, allowed values and response codes. Swagger is the tooling around that format; most often you meet Swagger UI — the same description as a list, with a send button right in the browser. Look for it at addresses like /swagger-ui, and for the file itself at /v3/api-docs.
Four things are read there: the list of addresses — an endpoint that the requirements never mention is already a finding; required fields and types — the basis for negative checks; enumerated values — ready-made equivalence classes; declared response codes with their error bodies — the expected result.
A collection is built from the same file: in Postman «Import» → file or link, and requests with sample bodies appear by themselves; for SOAP the WSDL is imported the same way. GraphQL carries its description inside the service — it returns types and fields in response to an introspection query.
A mismatch between description and behaviour is a defect: neighbouring teams write clients and automated tests against that description. State both sides: «in OpenAPI the phone field is required, the service accepts a request without it and answers 201»; what to fix — the code or the description — is up to the service owner.
Where people stumble
- Judging by the status.
200means «the server answered», not «it worked»: in SOAP and GraphQL the refusal arrives in the body with that same200, and in sloppy REST too. - Checking fields from memory. «I think it was
customerId» — and the check is green on a field the response does not contain. Names and types come from the schema. - Missing a partial GraphQL response.
datais not empty, the field you need isnull, and the reason sits inerrors. - Trusting a stale description. Swagger UI shows yesterday's contract while the service answers differently. The mismatch itself is a finding; cases are not bent to fit an outdated description.
In short
- The style of an API decides where the address, the action and the error are; the status is half of a check, the other half is always the body.
- REST: every resource has its own address, the action is the method, the outcome is in the status code; a verb in the path and
200with an error inside are deviations. - SOAP: an XML envelope, one address, the operation inside; a refusal arrives as
<soap:Fault>, often with code200. - GraphQL: one address, the client picks the fields; errors sit in
errorsat200, and the answer can be partial. - The contract comes from OpenAPI and Swagger UI, from WSDL or from the GraphQL schema; a mismatch with behaviour is a defect.
What to read next
- Client-server and HTTP — the methods, status codes and headers under all three styles.
- API testing in Postman — how to build a request by hand and check the response.
- Logging in: sessions and tokens — how to identify yourself in any of the styles.
- API tests in Python — the same checks without hands.