… and it's not just the hyperlinking that's problematic. (Yeah, MS's unRESTful "RESTful" APIs do a huge amount of coupling in the form of URL building.)

E.g., in Azure, which is also a "RESTful" API that has no idea what REST is about, MS completely misses Fielding points that most of the effort of definition should be spent defining the content / data's format, not things like URL structure. That way we can speak about MIME types / content-types, and know what structure we're describing. But Azure will happily describe in JSONSchema a single type, and declare that it is used for both PUT/GET, and … it's not. And discovering the additional constraints that exist on the type in the PUT is gleaned only through calling the API, certainly not through Azure's docs. And that's assuming you get a usable error in response.

JSONSchema is also a bit of a disappointment. On the one hand — yay, a spec? But on the other hand, it fails to capture so, so much. Half the fields in the type will be required … and the schema will say they're optional. Sum types of any kind are particularly badly handled, and half the time are just "string" though I think this is more of a failing on MS/Azure than JSONSchema, for simple string-like enums; but more complicated sum types, IDK if JSONSchema can't cut it or if MS just doesn't get it or what. For example, to instantiate a VM, the request body looks something like:

body: required struct {
properties: optional struct {
storageProfile: optional struct {
imageReference: optional struct {
communityGalleryImageId: optional string,
exactVersion: optional string,
id: optional string,
offer: optional string,
publisher: optional string,
sharedGalleryImageId: optional string,
sku: optional string,
version: optional string,
}
osDisk: optional struct {
createOption: optional enum { "Attach", "Empty", "FromImage" }
image: optional struct {
uri: optional string,
}
managedDisk: optionalStruct {
id: optional string,
// omitted fields
}
vhd: optional struct {
uri: optional string,
}
}
// omitted fields
}
// omitted fields
}
// omitted fields
}

I've listed only the fields used in determining where to source the VM's OS disk from. And it's nuts! "properties" and "osDisk" are actually required; if you specify "imageReference" or "image" or probably "vhd" (but I've never used that myself), "createOption" must be "FromImage", if you specify "managedDisk" it must be "Attach", and the docs don't describe what meaning "Empty" has. You can specify only one of those, because otherwise, you're saying to source the image from two things which would be nonsense (but is permitted by schema/docs?).

"imageReference" itself is really a sum type; you must specify (offer, publisher, sku, version[, exactVersion]), or communityGalleryImageId, or sharedGalleryImageId. You could image it being,

enum ImageReference {
FromMarketplace { offer: String, publisher: String, sku: String, version: String, exactVersion: Option<String> },
SharedGallery(String),
CommunityGallery(String),
}

And we've not even touched VHDs, managed disks, or VM images yet! And you don't need createOption.

I think, again, I'm going off what I've learned the hard way about how Azure works. I shudder to think what the validation logic looks like. (I'm also reading the docs. Reading JSONSchema is … painful to start with, but Azure's schema's directory layout structure makes it triply painful.)

But even that sum type is to miss the point of REST entirely. The RESTful definition would be:

image: URI-reference

and that's it. The Content-Type of the content at the provided URI provides the type of image that it is.

Oh and while I'm here: don't choose a boneheaded page size if you paginate an API call. Half of Azure's services will trickle-feed you 100 records at a time, and so the response body is like 60 KiB. Since the payload also has the next page's URI, your calls get decimated by latency. Some bad offenders: listing images in a repo in ACR gets ~ a phone modems worth of overall throughput. It takes minutes to download single-digit megabytes of image metadata. The Azure pricing APIs are similar: it's ~58KiB per page. The entire VM pricing data is something like 131 MiB, and that requires 2,235 HTTP calls to fetch.