> You cannot reasonably form API contracts (...) unless you can hide the internal representation.
Yes you can, by communicating the intended use can be made with comments/docstrings, examples etc.
One thing I learned from the Clojure world, is to have a separate namespace/package or just section of code, that represents an API that is well documented, nice to use and more importantly stable. That's really all that is needed.
(Also, there are cases where you actually need to use a thing in a way that was not intended. That obviously comes with risk, but when you need it, you're _extremely_ glad that you can.)