The reasoning models have one failure mode that cost me a day, and it is the worst possible shape a failure can take. Thinking tokens bill against max_tokens and are all emitted before a single character of content. So a budget sized for a normal model does not truncate your answer. It returns HTTP 200, finish_reason "length", and an empty string. Nothing in your error handling fires, because as far as your code is concerned the call succeeded.
My numbers: the detector's old 800 token budget failed 3 times out of 3 that way. At high reasoning effort I measured empty output in 3 runs out of 4, with the thinking alone consuming all 32768 tokens before any content appeared. I now carry max_tokens and reasoning effort with the model rather than the call site, because the two are not independent and nothing in the API tells you that.
Two fixes would have saved that day. Return reasoning token usage as its own field so a caller can see where the budget went, and warn when thinking consumes the entire allowance instead of returning a successful empty body. A 200 with nothing in it is the one response no one writes a handler for.