“Things that are made for humans” (UI) and “things that are made for machines” (API) have fundamentally opposite goals and requirements, and yet it is very common for UI to bleed into the world of API. I find that a developer who doesn't often ask themselves “is this for humans or machines?” is at great risk of falling into the trap of mixing them up. This happens most often with software whose users are also developers, making the distinction between the two worlds even harder.
Before going more in-depth, I'll motivate my point with a few examples of the phenomenon. Some are more obvious than others, but they are all ubiquitous in the industry:
Shells are human interfaces to the underlying operating system, therefore made for humans. Still, shell scripting is at the core of many systems today, like the entirety of SysV scripts before SystemD took over... and even SystemD unit files are filled with shell script snippets;
Urls are things humans can type into file- and web-browsers. They are composed of readable/printable characters and represent a bunch of fields that are delimited by separators instead of sizes since that is way more manageable by users.
And yet APIs everywhere take URL strings as parameters. Implicitly, those APIs want a protocol, a host, a port, a path and maybe some other parameters, but all they get is a very easy to type string, which they must parse and validate before being able to do anything with it.
HTML is UI. I'm using it right now to produce rich text, writing it manually. It is, afterall, a language, and is therefore pretty ok to type, its evaluation will even do some very human-friendly things, like merging or ignoring whitespace where one would expect.
Still, my server (a machine) will send this human-friendly file exactly as-is to your browser (another machine), instead of first transforming it into a tree of nodes, and all browsers must be able to parse and interpret these human-friendly strings. Worse still, there is a lot of logic out there automatically producing HTML (PHP, js, JSP, etc), dealing with the usual issues of text production (encoding, escaping, sanitizing), but it's likely that no human will ever read that raw human-friendly HTML.
Like HTML, SQL is a Language to query databases, meant to be written by humans. Humans would then feed those strings into some UI, which would convert it into something more manageable for machines
And also like HTML, it somehow became part of an API, and almost everything that interacts with a database must produce human-readable strings (susceptible to malicious injections, encoding issues, etc).
They come with a nifty REPL where you can type your quick and dirty code, without typing information, and immediately execute and see what happens. Ruby will even allow you to call functions without parenthesis, and python once had a very easy-to-type 'print' statement (not function). They are interpreted so you don't have to wait for compilation before you see your code execute.
All of those characteristics are great in a Shell that is interacting with humans, but create a lot of problems in code that is executing unattended and interacting with other machines.
JSON, YAML, XML, HTTP, etc. They all need to be parsed to get to the real values, use separators to delimit fields, can be typed by hand and read by humans and are extremely inneficient.
To help prevent further contamination of APIs with UI concepts, I find it useful to explore the differences between them, highlighting their constraints and goals.
UI operates under the limitation that inputs and outputs must be human-readable and human “writable” (often just typable on a keyboard). An API, on the other hand, can take and produce any kind of data, and dealing with human readable data is often inefficient (as it requires parsing) or even damaging (as, say, floats are converted to and from decimal short ascii strings).
This can be seen in, say, an API that takes a hostname as a string instead of an IPV4 address as an array of 4 bytes. Interestingly, the former has multiple levels of UI convenience bleeding into the API; the hostname is easier to type and easier to remember, plus it has to be DNS-resolved, which further depends on global, error-prone configuration, making it even less appropriate as part of an API.
Humans can't be expected to produce all arguments required for an operation at once, so UI's will guide users, step-by-step, into producing multiple arguments, which are then collected and used to execute some functionality. Machines, on the other hand, can collect as many arguments as needed before calling a function, and in fact it is much clearer for a developer to have functions explicitly require all their parameters upfront instead of relying on implicit arguments coming from some other source.
An example of this kind of UI bleedover is authentication; It is natural to have authentication information as an implicit, global value when humans are operating a system. On the other hand, an API that does not explicitly specifies credentials as an argument is confusing and error prone, yet very common (e.g.: cookies, tokens in environment variables or config files)
Errors in UI usage happen in front of a human, and therefore can be immediately corrected and retried. In fact, errors in UI usage are expected, and good UIs will try very hard to either interpret the inputs permissively or help the human fix the error. Errors in API usage, on the other hand, happen far away from the developer (in time and space) and are software defects that can't be immediately corrected. Further, being too permissive with the correctness of API parameters might lead to ambiguities or de-facto standards that diverge from the official ones.
I wonder if failing to distinguish these modes of usage was one of the motivations behind “throwing exceptions” in some programming languages; uncaught exceptions don't necessarily crash a program (specially in a REPL) and are easy to fix when they happen in front of the developer (just like UI errors), but are unproductive as part of APIs as they make failure states that should have been prevented in development time less obvious. Similarly, APIs that return overly friendly error messages instead of enforcing correct usage through its arguments are behaving less like good APIs and more as an IDE to developers (so, UI again).
It's more likely than not that your code will be interacting with other machines as API than directly with humans as UI so I'd say be very clear with what your program needs and very strict with what it will accept. If the word "parse" shows up anywhere, you might have already accidentally ventured into the world of UI.
I haven't had much time to think deeply about configuration files. On the one hand they feel like API; they will be interpreted by machines, they have special fields in special combinations that produce a valid file and it is easy to mess them up by writing them by hand, which leads me to think that they should rather be produced by a utility program (serializing a type-safe struct in code) instead of written by hand.
On the other hand, they could be seen as a DSL, meant to be edited multiple times by humans until they find the settings that achieve their goals. Plus, if the config file is always evaluated at load-time and verbose, polite errors are produced by the reading application, then it feels more and more like UI (at least as long as automatic config-file-generation is not part of the picture)
I'm still leaning towards config files being API rather than UI, but I do see the annoyance of having to fire a specific program every time one wants to modify some configuration.
This is an even bigger can of worms than configuration files, and I think it's worth thinking about. I haven't yet, but I'll post if ever I reach some good insight.