The API that could buy but not sell
Four buys placed clean. Three sells failed with the same cryptic error. The asymmetry was the diagnosis — buying credits your demat account, selling needs the depository's standing permission.
My execution pipeline had placed four live buy orders without a complaint. Then it tried to sell — a routine exit, nothing dramatic — and the broker’s API rejected it with a quantity-validation error that read like the system doubted I owned the shares.
I did own the shares. I retried days later: same error. A third attempt, same. Three sells, three identical failures, against four buys that had sailed through.
That asymmetry is the diagnosis, if you know where to look. A buy credits your demat account — no special permission needed to receive shares. A sell debits it, and in the Indian settlement system a broker’s API cannot debit your demat holdings unless you have granted a standing instruction at the depository level: the DDPI (Demat Debit and Pledge Instruction), the successor to the old power-of-attorney route. Mine read “inactive” in the profile response — one field, sitting in plain sight, explaining everything. Without it, every API sell dies at the depository’s door. Selling by hand still works, because the manual flow authenticates each debit with an OTP. The machine has no fingers for that.
Why this failure mode is nastier than it looks
My protective stops are not resting orders at the broker — they are monitored by the system, which checks live prices against the exit table three times a day and queues a sell on a breach. That design was itself a workaround for another entitlement gap. So walk the chain: breach detected correctly, exit sized correctly, order queued correctly, placement — fails. Detection worked; the trigger worked; the last inch was broken.
And every test had passed, because every test bought. The system had been proving the leg it used every day and assuming the leg it would need under stress. The exit path — the one that matters precisely when you are least calm — had never once been exercised end to end.
The fix was not code. It was a one-time authorisation flow at the depository, done by a human with an OTP. No script could close the gap; the only thing the system could do was fail loudly toward a human: any sell that bounces lands on a manual-orders list and triggers an email, so a real breach becomes a hand-placed exit rather than a silent one.
What I now believe about execution plumbing
Prove the exit before you rely on it. One real one-share sell, end to end, before the system is trusted with a stop. Reading the docs is not proving; placing a buy is not proving. The permissions model of market infrastructure is asymmetric, so the test must be too.
Enumerate entitlements as part of readiness, not as incidents. Scopes, standing instructions, product activations — each is a separate switch, each fails independently, and each announces itself only when first used. A readiness check that queries them all up front converts a market-hours surprise into a Tuesday-afternoon checklist item.
When errors are asymmetric across operation types, read the asymmetry. “Buys work, sells don’t” narrowed the search space to the one mechanism that distinguishes them. The error text was useless; the pattern of the errors was the whole answer.
Auditors have a phrase for the general failure: untested controls. A control that has never fired is not a control — it is a diagram of one.