diff --git a/proposals/4335-user-limit-exceeded.md b/proposals/4335-user-limit-exceeded.md new file mode 100644 index 00000000000..c3f8f7d8303 --- /dev/null +++ b/proposals/4335-user-limit-exceeded.md @@ -0,0 +1,262 @@ +# MSC4335: M_USER_LIMIT_EXCEEDED error code + +Currently, Matrix homeservers lack a standardized error code to indicate when user-related limits +have been exceeded. This creates inconsistent client experiences when homeservers need to reject +operations due to per-user quotas, rate limits, or resource constraints. + +Different implementations may return generic error codes like `M_FORBIDDEN` or `M_TOO_LARGE`, making +it difficult for clients to provide appropriate user feedback or implement proper retry logic. + +A concrete use case for this is the +[fair usage limits introduced on the matrix.org homeserver](https://matrix.org/homeserver/pricing/#usage-limits). + +This proposal introduces a new error code `M_USER_LIMIT_EXCEEDED` that homeservers can use to +signal when a user has exceeded their allocated limits, distinct from general rate limiting or +server-wide constraints. This improves the user experience by allowing clients to provide more +specific error messages and handle user-specific limitations appropriately. + +## Proposal + +This proposal adds a new [common error code] +`M_USER_LIMIT_EXCEEDED` to the Matrix specification. This error code should be returned when a user has exceeded +limits that are specifically associated with their account, such as: + +* **Storage quotas**: When a user has exceeded their allocated storage space for media uploads, + message history, or other persistent data. +* **Resource limits**: When a user has reached their maximum number of allowed rooms, devices, + or other account-scoped resources. +* **Feature limits**: When a user has exceeded usage limits for specific features (e.g., number + of public rooms they can create, number of invites they can send). +* **Account tier restrictions**: When a user's account type (free, premium, etc.) prevents them + from performing certain operations. + +The error response must also contain additional fields: + +* `info_uri` string (required) - an opaque URI that the client can link the user to in order to get more context on the + encountered limit. +* `soft_limit` boolean (optional, default `false`) - `true` means that the specific limit encountered can be increased. + Otherwise it is a hard limit that cannot be increased. +* `increase_uri` (required if `soft_limit` is `true`) - an opaque URI where the user can undertake actions to increase + the encountered limit. + +The `info_uri` and `increase_uri` are "opaque" in the sense that the homeserver implementation may choose to encode +information, such as the type of limit encountered, within the URI but it may do so using an encoding of its choosing. + +The homeserver may return *different* values for `info_uri` and `increase_uri` depending on what type of limit was +reached. + +The HTTP response code should be chosen based on the specification for the individual endpoint. For +example, the most appropriate code for [`POST /_matrix/media/v3/upload`] would be `403 Forbidden`. + +An example response body for the error might look as follows: + +```json +{ + "errcode": "M_USER_LIMIT_EXCEEDED", + "error": "User has exceeded their storage quota of 10GB", + "info_uri": "https://example.com/homeserver/about?limit_type=quota", + "soft_limit": true, + "increase_uri": "https://example.com/homeserver/upgrade" +} +``` + +For a hard limit: + +```json +{ + "errcode": "M_USER_LIMIT_EXCEEDED", + "error": "User has exceeded their storage quota of 10GB", + "info_uri": "https://example.com/homeserver/about?limit_type=quota" +} +``` + +### Applicable Endpoints + +As it is a [common error code], `M_USER_LIMIT_EXCEEDED` may be returned by any Matrix Client-Server API endpoint. + +For the purpose of illustration, practical examples could include: + +* [`POST /_matrix/media/v3/upload`] - When the server is enforcing a storage quota. +* [`POST /_matrix/client/v3/rooms/{roomId}/invite`] - When invite limits (like maximum participant count) are exceeded. +* [`POST /_matrix/client/v3/createRoom`] - When the number or type of rooms is constrained. + +### Distinction from Other Error Codes + +This error code is distinct from: + +* `M_LIMIT_EXCEEDED`: Used for general rate limiting that applies to all users or based on IP/client +* `M_FORBIDDEN`: Used for authorization failures or policy violations not related to usage limits +* `M_RESOURCE_LIMIT_EXCEEDED`: Used for server-wide resource constraints affecting all users +* `M_TOO_LARGE`: Used when a request is too large (file size, message length, etc.) regardless of user limits + +## Potential issues + +This error code does not specify the exact nature of the limit that was exceeded, which could +potentially lead to ambiguity. However, this is consistent with other Matrix error codes that +rely on the human-readable `error` field to provide specific details. Instead the `info_uri` +provides a way for the homeserver to apply arbitrary limits without the client having to understand +every type in advance. + +The homeserver can choose to provide localised and personalised content on the `info_uri` and `increase_uri` if it +wishes. + +The error code does not provide machine-readable information about current usage or limits, +which could be useful for clients to display progress bars or usage statistics. However, adding +such fields would require a more complex specification change and could be addressed in a future +MSC if deemed necessary. + +## Alternatives + +Several alternatives were considered for this proposal: + +### Use M_RESOURCE_LIMIT_EXCEEDED + +The existing [`M_RESOURCE_LIMIT_EXCEEDED`] looks very similar at first glance. + +However, this code is explicitly referring to a limit being applied to the *server* and not to the *user* themselves: + +> The request cannot be completed because the homeserver has reached a resource limit imposed on it. +> For example, a homeserver held in a shared hosting environment may reach a resource limit if it starts using too much +> memory or disk space. + +As such this error code is currently rendered by +[existing](https://github.com/element-hq/element-web/blob/c96da5dbf8e20ced4a512a03a75c91f8680e8d40/src/i18n/strings/en_EN.json#L1112) +[clients](https://github.com/element-hq/element-ios/blob/2dc7b76c44545b3d027cdf0196c6af6eba8932f4/Riot/Assets/en.lproj/Vector.strings#L615) +as something similar to: + +> This homeserver has exceeded one of its resource limits + +As such, I think this message would be confusing to users the interim whilst clients updated their implementations and +that a new error code would be the best way forward. + +### Add structured error information + +Instead of a simple error code, a more complex error format +could include machine-readable fields for limit types, current usage, and maximum limits. While +this would provide more information, it would require a more significant change to the error +response format and could be added in a future MSC if needed. + +### Multiple specific error codes + +Separate error codes could be introduced for different types +of limits (e.g., `M_STORAGE_LIMIT_EXCEEDED`, `M_ROOM_LIMIT_EXCEEDED`). However, this approach +would require many new error codes and doesn't provide significant benefits over a single code +with descriptive error messages. + +### Define specific endpoints + +Instead of making this a [common error code] instead it could be an +["other" error code](https://spec.matrix.org/v1.16/client-server-api/#other-error-codes) and have the specific endpoints +listed. + +A downside of this is that if a homeserver wished to introduce a new type of limit or quota that was not foreseen, then +another MSC would be required to introduce it. + +Instead, by making it a [common error code] the homeserver operator has flexibility over what types of limit they +choose without requiring further coordination with clients. + +### Use server side translations + +This could be similar to what is proposed generically by [MSC4176], but would need to support some form of markup rather +than just plain text in order to allow linking to external resources. + +If the homeserver wanted to communicate a "soft" limit then it could return something like: + +```json +{ + "errcode": "M_USER_LIMIT_EXCEEDED", + "error": "User has exceeded a media upload limit", + "messages": { + "en": "You have reached your daily upload limit. More information on the usage limits that apply to your account is available here. You can upgrade to a Premium account to increase the limits here.", + "fr": "Vous avez atteint votre limite de téléchargement quotidienne. Pour plus d'informations sur les limites d'utilisation applicables à votre compte, cliquez ici. Vous pouvez passer à un compte Premium pour augmenter ces limites ici.", + } +} +``` + +Similarly for a "hard" limit: + +```json +{ + "errcode": "M_USER_LIMIT_EXCEEDED", + "error": "User has exceeded a media upload limit", + "messages": { + "en": "You have reached your daily upload limit. More information on the usage limits that apply to your account is available here.", + "fr": "Vous avez atteint votre limite de téléchargement quotidienne. Pour plus d'informations sur les limites d'utilisation applicables à votre compte, cliquez ici.", + } +} +``` + +Comparison: + +* An advantage is that the homeserver can be as specific as it wishes with the messages. For example, the matrix.org + homeserver can specifically refer to the usage plans that are available rather than having to be generic. +* The size of the error response payload increases significantly. [MSC4176] has some discussion around using a header + such as `Accept-Language` or a query parameter to reduce the number of translations returned. +* A change in behaviour for clients: ordinarily the client picks the wording that is used to describe things in the + client User Interface, this would move responsibility to the server. However, this is no different from how the + [OAuth 2.0 API] makes the server responsible for translations relating to login and registration screens. + +### Server side translation with client side translation fallback + +This combines the benefits of above, with a client side fallback for servers that don't provide translations. + +The client would use the value from `messages` if a matching language is found, otherwise it can fallback to use generic +strings like in the main proposal. + +An example might be: + +```json +{ + "errcode": "M_USER_LIMIT_EXCEEDED", + "error": "User has exceeded a media upload limit", + "info_uri": "https://example.com/homeserver/about?limit_type=quota", + "soft_limit": true, + "increase_uri": "https://example.com/homeserver/upgrade", + "messages": { + "en": "You have reached your daily upload limit. More information on the usage limits that apply to your account is available here. You can upgrade to a Premium account to increase the limits here.", + "fr": "Vous avez atteint votre limite de téléchargement quotidienne. Pour plus d'informations sur les limites d'utilisation applicables à votre compte, cliquez ici. Vous pouvez passer à un compte Premium pour augmenter ces limites ici.", + } +} +``` + +So for English and French the client could show the specific messages provided by the homeserver, but for German the +client would pick the message with it's own translations. + +A downside of this is inconsistency in wording across languages depending on what the server provided. + +## Security considerations + +None as only adding a new error code. + +## Unstable prefix + +While this proposal is being developed and refined, implementations should use the following: + +* `ORG.MATRIX.MSC4335_USER_LIMIT_EXCEEDED` instead of `M_USER_LIMIT_EXCEEDED` +* `org.matrix.msc4335.info_uri` instead of `info_uri` +* `org.matrix.msc4335.soft_limit` instead of `soft_limit` +* `org.matrix.msc4335.increase_uri` instead of `increase_uri` + +For example: + +```json +{ + "errcode": "ORG.MATRIX.MSC4335_USER_LIMIT_EXCEEDED", + "error": "User has exceeded their fair usage limit of 2GB", + "org.matrix.msc4335.info_uri": "https://example.com/homeserver/about?limit_type=quota", + "org.matrix.msc4335.soft_limit": true, + "org.matrix.msc4335.increase_uri": "https://example.com/homeserver/upgrade" +} +``` + +## Dependencies + +None. + +[`POST /_matrix/media/v3/upload`]: https://spec.matrix.org/v1.16/client-server-api/#post_matrixmediav3upload +[`POST /_matrix/client/v3/rooms/{roomId}/invite`]: https://spec.matrix.org/v1.16/client-server-api/#thirdparty_post_matrixclientv3roomsroomidinvite +[`M_RESOURCE_LIMIT_EXCEEDED`]: https://spec.matrix.org/v1.16/client-server-api/#other-error-codes +[common error code]: https://spec.matrix.org/v1.16/client-server-api/#common-error-codes +[`POST /_matrix/client/v3/createRoom`]: https://spec.matrix.org/v1.16/client-server-api/#post_matrixclientv3createroom +[MSC4176]: https://github.com/matrix-org/matrix-spec-proposals/pull/4176 +[OAuth 2.0 API]: https://spec.matrix.org/v1.16/client-server-api/#oauth-20-api