Rewriting a 2013 API on .NET 10: What Stayed Compatible

I recently rewrote the MySafeInfo API, which I first built in 2013, on .NET 10. Other people's applications had been calling it for years. I wanted those applications to keep working without their developers having to revisit an integration just because I was replacing the implementation.

For the features I carried forward, that meant preserving the requests callers could make and the response formats they expected. The framework, internals, and data access could change substantially while the existing interface stayed familiar. The work was in maintaining that behavior through the rewrite, not just keeping the same endpoint names.

I made two deliberate exceptions.

  • I moved API keys out of URLs. Some of the original endpoints accepted an API key in the query string. That was convenient, but it put a credential in a place that could be recorded along with an otherwise ordinary request. Web servers and other infrastructure often log URLs. HTTPS protects a request in transit; it does not prevent a server from recording the URL after receiving it.

    OWASP recommends keeping API keys out of URLs. The rewritten API requires callers using a key to send it in the Authorization header, as described in the current documentation. Headers still need protection from logging, but moving the key keeps it out of the request URL.

    That required a change for callers using the old query-string option. I was willing to make that break because preserving the old behavior would have carried the same credential-handling problem into the new application.

  • I retired features with little use. The API had also accumulated optional parameters and modes over the years. When I looked at usage, many were barely used. They still needed to be maintained and tested, and some complicated the processing of requests that people did make regularly.

    I removed options whose maintenance and performance costs were out of proportion to their use. That gave me less code to maintain and improved performance. Usage data helped me make the decision, but low usage did not mean there could be no affected callers. Anyone depending on a removed option would still need to change their integration.

    This was a different judgment from moving the API key. I chose to retire those features because of the cost of carrying them forward. It was a tradeoff, and calling them optional did not make their removal backward compatible.

For the rest of the API, I kept the existing behavior. Moving the implementation to .NET 10 was work I had chosen to do. That alone was not a reason to give the developers using it more work of their own.