This proposal is to implement a namespace and version policy for routes in the MediaWiki REST API.
Problem
This RFC is intended to alleviate the following issues:
- APIs change and develop over time. We'd like a way to indicate that the API has had a change that is backwards-incompatible.
- Extensions are able to expose new API endpoints. They should not conflict with core endpoints, existing or future, nor should they conflict with other extensions.
Proposed solution
In general, routes exposed as part of the REST API in MediaWiki by T229661 should follow this pattern:
/v<version>/<rest of path>
Here version is the major part of the semantic version of the API interface (not MediaWiki!). As is typical for semantic versions, minor version numbers should be incremented when backwards-compatible changes are made (usually additional endpoints or fields in result objects), and major version numbers should be incremented when breaking changes are made.
(The minor version is not part of the path, but will be noted in documentation.)
For all the routes exposed by MediaWiki core we'll use the initial API version '1.0'.
rest of path is the RESTful path for the endpoint, like <type>/<id> or <type>/<id>/<attribute>. An example path:
/v1/revision/12345
Endpoints that have been deprecated because a new endpoint provides the same functionality better will have the Deprecation header set.
For REST API endpoints provided by extensions, the pattern will be:
/<component>/<rest of path>
Here, component is an URL-friendly string (short, lowercase Latin alphabet preferred) identifying the component that provides the API. This will usually be a lower-cased version of an extension name, like 'confirmedit' or 'popups'. It should not match the "v<digits>" pattern.
rest of path is up to the extension to decide. However, a version prefix for the extensions API version, independent of the core API version, is recommended since it is helpful to client developers. Extensions are developed independently so they should be able to change the versions of their API interfaces independently, also. For example,
/<component>/v<component major version>/<rest of path>
Interface strategy implications
Including a major semantic version number in the path of API calls dampens the rate of breaking changes and rearchitecture in the interface. APIs that use this technique are often append-only for the lifetime of a major version. That is, new endpoints are added and new properties of existing objects are added, but nothing is deleted from the interface.
Consider, for example, an API at version 1.0 with endpoints A, B, and C, each of which returns a JSON object with a number of properties a1, a2, ...
- Endpoint A: properties a1, a2, a3
- Endpoint B: b1, b2, b3
- Endpoint C: c1, c2, c3
If additional information is needed by the client for Endpoint B, a new property can be added, and a new version 1.1 released:
- A: a1, a2, a3
- B: b1, b2, b3, b4
- C: c1, c2, c3
Note that this is backwards compatible. A client application that was developed for API version 1.0 will still run and all the properties and endpoints it expects to find will still be there.
We can also add new endpoints, so that version 1.2 with new functionality at endpoint D looks like:
- A: a1, a2, a3
- B: b1, b2, b3, b4
- C: c1, c2, c3
- D: d1, d2, d3
Again, we've maintained backwards compatibility with previous 1.x minor versions.
If property a3 is of the wrong type (say, it's a string and should be an array) or the property name is misspelled or unclear (we have user_id instead of actor_id), instead of removing it and breaking backwards compatibility, we can add another property a4 with the right type and name, and deprecate property a3 in the documentation, so the interface version 1.3 is:
- A: a1, a2, a3 <deprecated>, a4
- B: b1, b2, b3, b4
- C: c1, c2, c3
- D: d1, d2, d3
Programs using the older 1.x interface definitions will continue to run correctly, even if they access a deprecated property.
It may happen that adding a duplicate property is too heavyweight at run-time (for example, we're renaming a property containing the full wikitext for an article, hundreds of thousands of bytes). In this case, it makes sense to add a new endpoint with the correct properties. If we did this with endpoint C, we'd add a new endpoint E, and deprecate C, making version 1.4:
- A: a1, a2, a3 <deprecated>, a4
- B: b1, b2, b3, b4
- C <deprecated>: c1, c2, c3
- D: d1, d2, d3
- E: e1, e2, e3
Again, programs depending on older interface definitions will continue to run correctly, even if they access a deprecated endpoint.
At some point, the collective drag of supporting and maintaining the deprecated endpoints and properties might be too much. Or, there might be a major rearchitecture of the underlying platform that we want to expose, or a security issue that can't be solved without removing a property entirely (say, for example, property d3 leaks user passwords, so adding property d4 and deprecating property d3 wouldn't fix the problem). At this point, we would create a new major version, 2.0, and drop all the deprecated properties and endpoints:
- A: a1, a2, a4
- B: b1, b2, b3, b4
- D: d1, d2, d4
- E: e1, e2, e3
The API endpoints here would be prefixed with /v2/ . Whether or not we continue to support /v1/ endpoints for some bounded period depends on the nature of the change (in case of a security problem, probably not), the amount of traffic we get to the API, and so on. It probably does not make sense to decide that at this time.
Unstable or experimental interfaces
Unstable or experimental interfaces should be implemented in extensions. Once the interface is stable, the same routes can be mounted in the main, stable namespace.
So, an endpoint for playing audio files could be at:
/audio/v0/sound-player?sound=12345
The endpoint could be changed without backwards compatibility because it has a "0" for its major version. So, the same functionality might move to:
/audio/v0/sound/12345/play
Once the interface has stabilized, it can either stay in the extension, maybe with a 1.x semantic version:
/audio/v1/sound/12345/play
Or it could be moved to the core routes namespace, if it's part of core:
/v1/sound/12345/play
From this point, it must retain backwards-compatibility or else cause a major version change in the core API.
Versions and namespaces in other APIs
For comparison, these are URL patterns for some other APIs that client developers may be familiar with.
API | Namespace | Version | Example root |
RESTBase | domain name by project | major version in path | https://en.wikipedia.org/api/rest_v1/ |
Stripe | none | major version in path, additional version by date in custom header | https://api.stripe.com/v1/ |
Twitter API | domain name (api, upload, ads-api, ...) | major.minor version in path | https://api.twitter.com/1.1/ |
none | optional major.minor version | https://graph.facebook.com/v4.0/ | |
Twilio | none | date-based version | https://api.twilio.com/2010-04-01/ |
domain name (maps, googleads, ...) | major version in path | https://googleads.googleapis.com/v2/ | |
Apple | domain name (appstoreconnect, ...) | major version in path | https://api.appstoreconnect.apple.com/v1/ |
Uber | none | major.minor in path | https://api.uber.com/v1.2/ |
Sendgrid | none | major version in path | https://api.sendgrid.com/v3/ |
Amazon Web Services (AWS) | domain name (ec2, s3, ...) | varies, usually date as "Version" parameter | https://ec2.amazonaws.com/?Action=...&Version=2016-11-15 |
Microsoft Graph | none | major.minor in path | https://graph.microsoft.com/v1.0/ |