What a great question! API design is certainly subjective and, while there are some guidelines and best practices, it’s ultimately up to each organization to determine design practices that best suit their needs. With that said, here are my thoughts:
Your existing approach to pre-processing sounds good to me as long as you don’t anticipate needing the pre-processing step to be invoked on its own. If you anticipate needing pre-processing independent of running the actual model, you could separate that step into its own endpoint and then invoke it from the model endpoint(s).
Instead of building a separate endpoint for each country, you could use a single endpoint with a dynamic route that would essentially act the same way for the end user, but prevent you from maintaining duplicate logic in the API.
#* Get country prediction
#* @param country Country code
#* @get /<country>/predict
function(country) {
# Function logic
}
In this implementation, API consumers still send GET requests to the urls you’ve specified (/au/predict and /nl/predict) but all of the logic is handled in a single endpoint. Within that endpoint, you can determine what the appropriate response is based on the value of country.
In response to your other questions, nesting services in this way is certainly a common design. This type of architecture allows you to separate each piece of logic into its own service, and then each service can either be invoked in isolation or orchestrated together as part of a more complex process. This type of architecture can also simplify the maintenance requirements for complicated systems, since each piece can be updated in isolation.
There are a couple of ways you can enable a user to decide whether or not they want the results of API Y to be included. The first option is to include a query parameter, something like include_y. You could check for this parameter before submitting a request to API Y.
#* Preprocessing
#* @param include_y Include results from Y?
#* @get /preproc
function(include_y = FALSE) {
if (include_y) {
# Include Y
} else {
# Don't include Y
}
}
API users could then query this endpoint by submitting a GET request to api.path/preproc?include_y=TRUE to include API Y or api.path/preproc?include_y=FALSE to not include API Y.
An alternative option is to include a header indicating the same thing.
#* Preprocessing
#* @get /preproc
function(req, res) {
if (req$HTTP_INCLUDE_Y) {
# Include Y
} else {
# Don't include Y
}
}
In this case, we're not checking a parameter, but we're instead checking a header attached to the req object. A request made to this endpoint would look something like this:
GET /preproc HTTP/1.1
Host: <API Host>
Include_y: TRUE
Hope that's helpful! Don't hesitate to fire away with more questions.