I figured it out!
I had this in my AppStart, because I wanted the Xml serializer not the DataContract serializer:
GlobalConfiguration.Configuration.Formatters.XmlFormatter.UseXmlSerializer = true;
HOWEVER… apparently there is something about my model that makes the Xml Serializer think it can’t serialize it. I am guessing that is causing WebAPI to decide to use the JSON formatter instead.
It’s completely non-intuitive that this harmless looking setting could actually affect which formatter is used. Hope the WebAPI people see this 🙂
Some kind of tool that let you understand the inputs and outputs of content negotiation process so you can debug issues like this would be nice.