Your API Returned 200. Your Integration Is Still Broken.
A green status code can hide a broken integration. Here are five checks that reveal what your happy-path request misses, with a small contract test you can run today.
The request is green. The dashboard is blank.
Here is a hypothetical response from an account endpoint:
{
"id": "acct_42",
"displayName": null,
"plan": "free"
}
The server returned 200 OK. The frontend called displayName.trim().
Both teams can point to something that worked. Neither team has a working integration.
A successful HTTP request is only one part of a successful API interaction. The other part is whether the response means what the caller expects.
We build Powerduck, an OpenAPI workspace. This distinction is central to how we think about API debugging. But you do not need our tool to apply the checks below.
1. Test the shape you promised
Start with the fields your consumer actually uses. In OpenAPI 3.1, this schema requires an account ID and explicitly allows a nullable display name:
type: object
required: [id, displayName]
properties:
id:
type: string
displayName:
type: [string, "null"]
plan:
type: string
Required and non-null are separate decisions. A field listed under properties is not automatically required, and a missing property differs from a property whose value is null. The JSON Schema object reference explains these distinctions.
Now the frontend has an explicit decision to make: show a fallback name, hide the label, or reject this state. That decision should not depend on which test account someone happened to use.
2. Keep an example that makes the UI uncomfortable
A beautiful example is often a weak test fixture:
{"id":"acct_42","displayName":"Ada","plan":"pro"}
It exercises the easiest path. Keep that example, then add the nullable version. If the contract allows omission, add a missing-field example too.
For a list endpoint, try an empty result. For a display name, try a long string. For pagination, try the last page. For a permission-dependent field, compare callers with different access levels.
Do not generate edge cases at random. Pick states the application can actually produce, then connect each fixture to a documented rule.
An example demonstrates one possibility. It does not establish every possibility.
3. Check what arrived before parsing it
The following is a small Node.js check for a hypothetical local service. It makes no claim to replace a schema validator:
import assert from "node:assert/strict";
const response = await fetch("http://localhost:3000/accounts/acct_42");
assert.equal(response.status, 200);
const mediaType = (response.headers.get("content-type") ?? "")
.split(";", 1)[0].trim().toLowerCase();
assert.equal(mediaType, "application/json");
const body = await response.json();
assert.ok(body !== null && typeof body === "object");
assert.equal(Array.isArray(body), false);
assert.equal(typeof body.id, "string");
assert.equal(Object.hasOwn(body, "displayName"), true);
assert.ok(body.displayName === null || typeof body.displayName === "string");
Run it against a seeded development or test environment. The point is to turn an assumption into a failure you can reproduce.
For a larger API, derive validation from the OpenAPI document instead of maintaining hundreds of hand-written assertions. Keep business assertions alongside it: a response can match its schema and still describe the wrong account.
4. Give failures their own contract
Try the request with an invalid ID, without credentials, and with a caller who lacks permission.
You are checking two things: whether the service refuses the operation appropriately, and whether the consumer can understand that refusal.
A generic “request failed” message is sometimes all the user should see. Your client code may still need a stable error identifier to distinguish an expired session from a validation problem.
Write down the failures your implementation intentionally supports. Avoid adding an impressive list of status codes that the server never returns.
The OpenAPI Response Object lets you describe response content and headers. Use that space for real success and error behavior, not just a placeholder success response.
5. Decide whether the implementation or the document is wrong
When a check fails, updating the schema to match the latest payload is tempting. Sometimes that is the right fix. Sometimes it quietly converts a regression into a promise.
Ask three questions before changing either side:
- What behavior did existing consumers rely on?
- Was this change intentional?
- Which test will prevent the disagreement from returning?
The answer might be a server fix, a corrected contract, or a coordinated migration. “Make the warning disappear” is not enough information to choose.
Try this on one endpoint
Pick an endpoint your frontend uses every day. Save its normal response, one valid edge case, and one expected failure. Compare all three with the contract. Add a regression check for the first disagreement you find.
In Powerduck, the OpenAPI document connects editing, debugging, and documentation. That is useful because a correction should reach the next person reading or calling the API. It still takes engineering judgment to decide what the contract should promise.
What is the smallest response change that has broken one of your integrations? A missing field, a new enum value, or something less obvious?
Generated with AI from Powerduck product-development discussions. Examples use fictional data.

