There are a number of reasons, but one very important reason is that the Operating System has to know how to read the series of bytes that make up your program into memory, find the libraries that go with that program and load them into memory, and then start executing your program code. In order to do this, the creators of the OS create a particular format for that series of bytes so that the OS code knows where to look for the various parts of the structure of your program. Because the major Operating Systems have different authors, these formats often have little to do with each other. In particular, the Windows executable format as little in common with the ELF format most Unix variants use. So all this loading, dynamic linking and executing code has to be OS specific.
Next, each OS provides a different set of libraries for talking to the hardware layer. These are the APIs you mention, and they are generally libraries that present a simpler interface to the developer while translating it to more complex, more specific calls into the depths of the OS itself, these calls often being undocumented or secured. This layer is often quite grey, with newer "OS" APIs are built partially or entirely on older APIs. For example, in Windows, many of the newer APIs Microsoft has created over the years are essentially layers on top of the original win32 APIs.
An issue that does not arise in your example, but that is one of the bigger ones that developers face is the interface with the window manager, to present a graphical UI. Whether the window manager is part of the "OS" sometimes depends on your point of view, as well as the OS itself, with the GUI in Windows being integrated with the OS at a deeper level, while the GUIs on Linux and OS X being more directly separated. This is very important because today what people typically call "The Operating System" is a much bigger beast than what textbooks tend to describe, as it includes many, many application level components.
Finally, not strictly an OS issue, but an important one in executable file generation is that different machines have different assembly language targets, and so the actual generated object code must different. This isn't strictly speaking an "OS" issue but rather a hardware issue, but does mean that you will need different builds for different hardware platforms.