Every project seems to acquire a favorite test request.

The credentials are valid. Every field is present. The identifiers point to records that exist. The payload is short, clean, and carefully typed.

You hit Send. It works. You hit Send again tomorrow. It still works.

That request is useful as a baseline. It becomes a problem when it is the only request anyone runs.

Change one thing, not everything

Consider a fictional invitation endpoint:

{
  "email": "alex@example.com",
  "role": "member",
  "message": "Welcome to the project."
}

Assume the contract requires email, supports a defined set of roles, and makes message optional.

Do not immediately build a giant randomized test suite. Duplicate the baseline and change one input. When it fails, you will know which assumption to investigate.

Start with omission:

{
  "email": "alex@example.com",
  "role": "member"
}

Then try an explicit null:

{
  "email": "alex@example.com",
  "role": "member",
  "message": null
}

Those are separate cases. An optional property can be absent without allowing null. The JSON Schema object documentation explains the distinction between presence and value validation.

Your expected result should come from the intended contract. If the implementation disagrees, investigate before changing either side.

Use a small matrix of meaningful variations

For this endpoint, a first pass might look like this:

VariationQuestion to answer
Omit messageDoes optional really mean optional?
Send message: nullIs null allowed, rejected, or normalized?
Send an unsupported roleDoes the service enforce the documented choices?
Use a caller without invite permissionIs authorization enforced independently of valid input?
Invite the same address twiceIs the duplicate behavior intentional and understandable?
Send a message at the documented size limitDo validation and storage agree?

Run invitation tests against a controlled environment with outbound email disabled or redirected to a test sink. Otherwise, a useful test can become an accidental message to a real person.

Notice that the matrix contains questions, not guessed status codes. Decide the expected behavior with the implementation and contract in front of you, then turn that decision into an assertion.

Read the failure, not just the status

If the server rejects an unsupported role, can the client explain what went wrong?

If the caller lacks permission, does the response avoid revealing information they should not see?

If the invitation already exists, can the frontend distinguish that outcome from a temporary service failure?

A red response is not automatically a failed test. An intentional, well-described rejection may be exactly what should happen. Save that result as carefully as you save the successful one.

Keep the cases that teach you something

Manual exploration is valuable because you can notice surprises that an existing assertion does not describe. Once you understand a surprise, give it a repeatable check.

Name saved requests by behavior: “message omitted,” “unsupported role,” or “caller cannot invite.” Names like “test 2 final” force the next developer to rediscover what the request was meant to prove.

Keep credentials and environment-specific identifiers outside the shared example. Make the setup clear enough that someone else can reproduce the result without borrowing your session.

In Powerduck, you can open an OpenAPI document or import existing requests, then work between the contract and the request debugger. That makes a useful loop: inspect a rule, exercise it, and fix the disagreement while both are in view.

Try it before your next release

Take the request you run most often. Remove one optional field. Change one allowed value to an unsupported one. Use a caller with fewer permissions.

You may find that everything behaves exactly as documented. Good: you now have evidence for three assumptions that your perfect request never tested.

What is the first variation you try when a teammate says an endpoint is ready?