Instead of pushing the external, side-effecting thing down the stack, I put it up the stack and call the logic that generates the data for it from there. Now I don't need to mock an interface, I just need to test data in/out.
If you do this consistently, then you have much of the side effecting code residing next to each other per process/input handler etc.
It's not always feasible or the right thing. But it's a good default way of structuring code and makes testing much more straight forward. And it makes more clear "at a glance" what happens because you can reason more locally about the side effecting parts.
> It also means you are less depedent on the concrete implementation of the API and don't leak types and behaviour from systems you don't control into your codebase
You are always dependent on that. Whether you hide it down the stack or lift it up, you still need to put your data into a specific shape, do the same essential checks etc.