On Oct 10, an outside tester applied to test Kilawatt Cloud's x402 pay-per-job GPU endpoint. We sent a small amount of USDC to their wallet. They bought one 60-second GPU job for one cent, then sent back nine findings about how the endpoint behaves when things go wrong. Two of the nine could cost a buyer money.
We changed all nine in code within hours. We have since re-checked five of the eight headline fixes against the live service. Three still need a paid call to verify. This post is the full account, including the parts that are not proven yet.
The two that could cost a buyer money
1. Malformed JSON became a default H100 job. A request with a broken body was not rejected. It was quietly priced as a default H100 job at about 38 cents. An agent that signs payments automatically could have paid for work it never asked for.
2. A replayed payment returned a vague 402. Resending an already-settled payment did not return a clear conflict. A client that lost its response could not tell "already paid" from "failed", which risked a duplicate payment or a lost one-time API key.
Both are fixed in code. The first one is also confirmed live.
All nine findings and what changed
| # | What the tester found | What changed | Checked |
|---|---|---|---|
| 1 | Broken or non-JSON requests quoted as a default H100 job | Body parsed first. Invalid JSON, a non-object or an array returns 400 invalid_json. Wrong content type returns 415. Empty body still gets documented defaults. |
Live |
| 2 | Replayed settled payment returned a vague 402 | Clear 409 payment_already_used with the original transaction and job where known. A unique transaction-hash check remains as a backstop. |
Code |
| 3 |
price_changed code missing |
Both protocol versions return price_changed. Version 2 also returns the new required amount. |
Code |
| 4 | Unfunded wallet or bad signature gave an opaque error | One stable code, insufficient_funds_or_invalid_signature, plus a hint. It cannot tell the two causes apart, so this is only partly fixed. |
Code |
| 5 | Unknown GPU name returned 503, no list of valid names | 400 unknown_gpu_type with the 16 valid names. Oversize requests return 400 size_not_available. 503 stays for real capacity misses. |
Live |
| 6 | GET on the endpoint returned website HTML, no discovery file | JSON 405 with an Allow header. /.well-known/x402 returns a JSON manifest. |
Live |
| 7 | Misleading errors when a payment header was in the wrong place | 400 wrong_payment_header. Unreadable amounts return 400 malformed_payment. |
Code |
| 8 | Job receipt had no ready time, end time or runtime | Receipt returns created_at, ready_at, ended_at, runtime_seconds. |
Live |
| 9 | Internal payment ID and exact supplier cost in responses | Payment ID removed. Supplier cost still returned, but rounded and labeled approximate. Hiding it fully is under review. | Live |
"Live" means we sent a request to the public service on Oct 11 and read the response. "Code" means the change is in the source and has not yet been exercised on the live service. Items are numbered in our fix order, not the tester's order.
How we re-checked the live ones
You can run these yourself. They are plain requests, nothing paid.
# Malformed body and a bare array: expect 400 with code invalid_json
curl -si -X POST https://www.kilawattcloud.dev/api/public/x402/exec \
-H "Content-Type: application/json" -d '[1,2,3]'
# Wrong content type: expect 415 unsupported_media_type
curl -si -X POST https://www.kilawattcloud.dev/api/public/x402/exec \
-H "Content-Type: text/plain" -d 'hello'
# Wrong method: expect 405 with Allow: POST, OPTIONS
curl -si https://www.kilawattcloud.dev/api/public/x402/exec
# Discovery manifest: expect 200 and JSON
curl -si https://www.kilawattcloud.dev/.well-known/x402
On Oct 11 between 05:05 and 05:10 UTC these returned 400 invalid_json, 415, 405 with Allow: POST, OPTIONS, and a 200 JSON manifest. An unknown GPU name returned 400 with the list of 16 valid names. A job receipt requested without a key returned 401. With the account's own key it returned 200 and the new timing fields, with no internal payment ID.
For the receipt of a 1,200-second job, the new fields read: created 03:28:24, ready 03:29:05, ended 03:49:02, runtime 1,238 seconds. The runtime includes the short lag before a machine is shut down after its booking runs out.
What we have not proven
-
Three headline fixes are not live-verified. The replay 409, the
price_changedcode and the short-wallet code (items 2, 3, 4) are in the source. Checking them live needs a paid call or a deliberately failed payment. They are marked code only. - Item 4 is only partly fixed. One combined code covers an unfunded wallet and a bad signature. A buyer cannot tell which one it was.
- Supplier cost is still visible. It is rounded to the nearest 10 percent of the billed amount and marked approximate. A buyer can still estimate our margin from it.
- Untested paths. The refund when no capacity is available after payment, credit for an overpayment and recovery of the one-time API key after a lost response were not tested in this round.
- One tester, one paid job. We did not verify the tester's identity or claims, and this is not a penetration test or a security audit.
- What held up is the tester's word. They reported that refusals on a wrong recipient or network, schema validation, CORS, rate limiting and the price re-quote guard behaved correctly. We did not re-test those for this report.
The paid job itself was funded by us (3 USDC sent to the tester's wallet, one cent spent), so it counts as sponsored, not customer revenue.
Lessons if you are building an x402 endpoint
- Parse the body before you price anything. A default quote on a broken request is a money bug, not a cosmetic one.
- Make replays boring. Return a clear 409 with the original transaction, so a client that lost a response can recover.
-
Give every failure a stable machine-readable code.
price_changed,unknown_gpu_typeandwrong_payment_headerare cheap to add and save agents from guessing. - Publish a discovery manifest and return JSON on the wrong method. Agents should never have to parse website HTML.
-
Put timing in the receipt.
ready_at,ended_atandruntime_secondslet buyers audit what they paid for. - Do not leak internal IDs or exact costs in buyer responses.
Read the full report
The full report has the three charts, the paid-job record with its transaction hash, the method, and the limits:
https://kilawattcloud.dev/super-intelligence
Look for "Outside Tester Report 1: Nine Findings, Nine Changes" under Security.
If you want to test the endpoint, or you find something we got wrong, tell us: hello@kilawattcloud.dev
Kilawatt Cloud is a trade name of Right Recruit LLC. This is a company-prepared report, not independent reporting.





Top comments (0)