Symptom
The approval request is a PUT with nothing to send. The response is 411 Length Required with an HTML body (<html><title>411 Length Required</title>). Every other response from this API is JSON, so the first reading is "the relay is broken." It isn't.
Cause
Common HTTP clients send no Content-Length header at all when you give them no body. The gateway rejects a PUT without a length with 411. The HTML is the gateway's own error page, passed through the relay untouched. The relay didn't strip a body; there was never a body to carry.
★ Having no body and not declaring a length are different things. HTTP allows the first; the gateway refuses the second.
Fix
Don't invent a payload ({} is an arbitrary value too). Send an empty body and declare length 0 explicitly.
body = b''
headers['Content-Length'] = '0'
(If the request signature covers only method, path, and date, changing the body leaves the signature alone; this fix doesn't touch auth.)
There are two gates
Fixing the client didn't finish it. The request leaves through a fixed-IP relay, so there is one more gate. The relay set curl's POSTFIELDS conditionally when the body was empty.
if ($body !== null && $body !== '') curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
It reads as defensive. "Why set an empty body?" That is the trap. Setting an empty body is what declares length 0, so here the condition is the defect. Always set it, even when empty, and discard the caller's Content-Length in favor of one recomputed from the actual bytes (a mismatch and the gateway cuts you off).
★ Every hop adds a gate. Fix your side and the relay can still drop it. The first fix's mock exercised only the direct leg and went green, which is why this was found late.
Blast radius
One 411 spread four layers deep: approval request never lands → item stays in draft → draft has no reason text, so the watch classifier files it as "unclassified" → the dashboard card can't show it (① in the previous entry). Two days without appearing anywhere on screen.
Verification
★ The error moving to a different layer (411 → 401) is the evidence of repair. After the relay deploy, the same call returned 401 instead of 411. 411 gone means curl attached a length; the remaining 401 belongs to the auth layer. The transport layer is finished, so you can move on to the next one. An error that moves is better than one that goes quiet. Then success: true, then the item entered the review queue: each step is the evidence for the next.