If, after doing so, you find that you need another program with different hardcoded information, turn your program into a function. Take the implementation as an argument, take the URL as an argument, take the information you need as an argument.
And then just call that function with the arguments you prefer, each time you need a program with different "configuration".
You don't need to load those arguments from some file on disk, or by querying some database or service, or even by taking them as command line parameters.
Just make a separate program for each case.
For a batch job, this might mean a few different executables, tweaked and rebuilt frequently over time as one's needs change.
For a user-facing application, this might mean that each user runs their own custom executable, compiled for or by them, with pre-compiled shared libraries common between all users.
For a daemon providing some network service and connecting to other services, this might mean 10 or so different executables, which are run in the 10 or so different availability zones or datacenters or sorts of machines on which this daemon is deployed. Or for a more heterogeneous deployment, it might mean many thousands of different executables, deployed to many thousands of different environments, each layered on top of a common image with shared libraries identical between all deployments.
If you want to share information between multiple programs with different configurations, share it in the same way you share code: with a library.
If you want to make your configuration more dynamic, write code to dynamically determine the arguments to pass.
If you want to make rapid changes and don't want to wait for builds, call the function from an interpreted language, or rely on fast incremental builds.
If you want to see what arguments are being passed to your function in your program, use logging and debuggers, according to your preference.
If you want to roll back to an old configuration, just roll back to an old binary, which you need to be able to do anyway.
Code written in this way in your actual programming language can be easily constrained to only allow valid configurations; that's much more difficult for code written in a configuration DSL, like JSON, YAML, or Dhall.
But in any general-purpose programming language, implementations are first class values which can be passed around and manipulated, whether as an object, a module, a struct of function pointers, or something else. One can write a function (or a template, a functor, a macro, or something else) which takes the implementation directly as an argument, with no possibility of issues due to mismatches between a string identifier and the actually available implementations.
And the resulting encoding is compact in size. For example, instead of large text strings for keys and values, a binary just stores pointers at the right offsets in structs.
But static checks in general-purpose programming languages, such as type checkers, perform "validation" of the "schema" of our configuration for free, wherever we pass arguments to functions. Required arguments must be present, and even must be of the correct type, and there's no need to keep our code in sync with a schema.
But a general-purpose programming language lets you pick which level of restrictiveness you want, on a case-by-case basis, without changing language. Just use your favorite language-level sandboxing tool to evaluate the code so that it can't perform IO. Then if you want the config to be able to include other files, pass in the capability to do that; if you want the config to be able to import files from the internet, pass in the capability to do that; and so on. Preventing the use of variables is, admittedly, a bit trickier in general-purpose languages; a language where variable binding is a capability could do it.
But general-purpose programming languages, unlike the typical configuration DSL, come with lots of semantic refactoring tools to manage these issues. And general-purpose languages can be inspected and analyzed with straightforward code; no need to work with ASTs. Finally, programmatic modification is less necessary when you're able to express better abstractions for your "configuration" in the first place.
Writing code in your general-purpose language is easier, faster, and better than writing separate configuration.
Of course, this is all easier with faster and more powerful build systems, like Nix, or with interpreted or fast-building languages, like Python, or with, at least, shared libraries and a fast link step, like dynamic libraries in C.
If you're on a slow, weak, and hard-to-use build system, with a slow-building language, and you have slow linking, then you probably want to fix one or more of those issues first; although if your programs are small, even those issues are not necessarily prohibitive.
And you might also be in a corporate environment, where code changes require an extensive and painful process, but configuration changes can be made relatively easily. If so, perhaps this article will help you improve the situation.
Many have written about this before; here is another article about this.