Testing an A2A JSON-RPC Error Inside a 200
Quick answer: An A2A call can return HTTP 200 while the body carries a JSON-RPC error. The transport succeeded, but the method did not, and a test that only checks the status will treat the failure as a pass. Read the payload, distinguish a JSON-RPC result from an error, and assert the protocol-level outcome rather than the HTTP code alone.
Why the two layers can disagree
A2A runs over JSON-RPC, and the HTTP layer is only the carrier. The server can accept the request, return a healthy 200, and still report inside the body that the method was rejected, a parameter was invalid, or the task could not be created. There is no rule that forces a protocol error to surface as an HTTP error code.
That means a green status is ambiguous until you open the body.
Two outcomes in one response
| Layer | What you read | A 200 can show |
|---|---|---|
| HTTP | Status code | 200, transport accepted |
| JSON-RPC | result or error |
Either a method result or an error |
A 200 with a result is a full success. A 200 with an error is a transport success and a method failure, and the two have to be read together.
Say a caller sends a task creation request with a missing required field. The server accepts the HTTP message and returns 200, because the transport itself worked, but the JSON-RPC body carries an error naming the invalid parameter. A test that reads only the status records a pass, while the method was rejected and no task exists. When the same request is sent with the field complete, the body carries a result with the task reference, and the two cases are distinguishable only inside the payload. The test should assert a result for the valid case and the specific error code for the invalid one rather than treating both as success.
How to test it honestly
Build the request in the Debug surface with the generated body and the A2A-Version header, then assert at the right layer.
- Send the request and confirm the HTTP status.
- Open the body and check whether it carries a
resultor anerror. - If it is an
error, read the error code and message. - Assert the protocol outcome rather than treating 200 as a pass.
- Keep the transport and protocol findings separate in the report.
A test that stops at the status line misses exactly this failure, and it can report a suite as green while methods are being rejected. The assertion has to look inside the payload to be meaningful.
Where this fits the broader pattern
This is the same boundary that keeps a 200 response from being trusted when a field assertion fails. A transport-level success says the message arrived and was accepted at the wire, and it says nothing about whether the method behaved. Treat the two as separate questions, and verify the contract at the layer where it actually lives.
When the error is intermittent, such as a task that is rejected only under certain inputs, keep the request and the full body in the report so the failure is reproducible rather than reduced to a single status label.
Powerduck keeps the JSON-RPC layer visible in the Debug surface rather than collapsing it into the HTTP status, so the error does not hide behind a 200.
Frequently asked questions
Is a 200 always a successful A2A call? No. The body can carry a JSON-RPC error even at 200, so read the payload and check for result or error.
What should the assertion target? The protocol-level result inside the body, not just the HTTP status code.
Why does the server return 200 with an error? Because HTTP is the transport and JSON-RPC carries its own method outcome, which is not required to map to an HTTP error.
The bottom line
Never let an HTTP 200 stand in for a method-level pass. A2A can return a healthy transport status while the JSON-RPC body reports an error, and the honest test reads both layers and asserts the protocol result. Build the request with the version header, open the body, and keep the two findings separate. Treating the carrier as the contract is how rejected methods get reported as green. Continue in the A2A guide.