Both APIs use POST with JSON bodies. The key differences are in the URL and body structure.
What the SDK is hiding
When you build the request body by hand, you see the contract directly: the JSON keys (model, input, tools, instructions, max_output_tokens, stream, tool_choice) and their exact spellings. No SDK helper renamed something for you. No default snuck in that you didn't write.
This is the level at which you debug 'why does my SDK call work but my hand-rolled call return 422?' The answer is almost always one wrong field name or a missing type: "function" on a tool. Once you've built the request body manually a few times, the SDK's behavior stops feeling magical.