Rejected position instructions now carry a machine-readable rejection object
next to the existing rejection_reason text. Each rejection has a stable
reason code (for example DNE_NOT_ON_EXPIRY, INSUFFICIENT_POSITION,
OPTIONS_LEVEL_EXCEEDED, or EXERCISE_PAST_CUTOFF), a domain, and a
metadata map of reason-specific parameters (for example available and
requested, or expiry and business_date), typed as a string-to-string map.
Branch on rejection.reason instead of parsing rejection_reason. The same
rejection is returned when reading instructions (GET) as well as on submit
and cancel, and the human rejection_reason on reads is the same canonical
message, so a rejected instruction looks the same however you fetch it. The
field is absent on accepted rows, on rejections that carry no structured code,
and on instructions rejected before this shipped.
The HTTP status for a rejected instruction now reflects the specific cause. A
well-formed instruction that is rejected on its merits — for example an exercise
after expiry day, a do-not-exercise or contrary-exercise outside expiry day,
insufficient position, an options-level limit, or a clearing rejection — now
returns 422 Unprocessable Entity. This applies to a submission where every row
is rejected for such a reason, and to a cancel that arrives too late or targets a
terminal instruction. These cases previously returned 400 Bad Request. Duplicate
(409), clearing-unavailable (503), and internal-fault (500) responses are
unchanged. If your client keys on 400 to detect these rejections, update it to
handle 422.
POST /v1/accounts/{account_id}/positions/instructions
DELETE /v1/accounts/{account_id}/positions/instructions/{instruction_id}
GET /v1/accounts/{account_id}/positions/instructions