Skip to content
150 changes: 150 additions & 0 deletions proposals/4335-user-limit-exceeded.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
# 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](https://spec.matrix.org/v1.16/client-server-api/#common-error-codes)
`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) - a URI that the client can link the user to in order to get more context on the error
* `soft_limit` boolean (required) - `true` means that the specific limit encountered can be increased. `false` means
that it is a hard limit that cannot be increased.
* `increase_uri` (required if `soft_limit` is `true`) - a URI where the user can undertake actions to increase the
encountered limit.

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",
"soft_limit": false
}
```

### Applicable Endpoints

This error code can be returned by any Matrix Client-Server API endpoint where user-specific limits
might be enforced. Examples could include:

* [`POST /_matrix/media/v3/upload`] - When storage quota is exceeded
* [`POST /_matrix/client/v3/rooms/{roomId}/invite`] - When invite limits (like maximum participant count) are exceeded

### 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.
Comment on lines +91 to +95
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we include a type field that gets passed to info_uri as a query parameter or something?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just encode the necessary information in info_uri straight away on the server side?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just encode the necessary information in info_uri straight away on the server side?

Exactly. The intention is that the URIs are opaque and the homeserver may choose to put a type or similar field in there.

I've attempted to clarify this in 4dd453b.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just encode the necessary information in info_uri straight away on the server side?

Well I assume you want to tell people what limit they've passed, would each of those be a separate info_uri? Feels like it ties your homeserver implementation tightly to whatever is at those URIs.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not understanding how just saying they're "opaque" resolves this. This makes it very tied to particular homeserver implementations.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Screenshots are at the top of this PR. #4335 (comment)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The client knows what operation failed so can give some context. Like, in the screenshots, it can say that "upload failed" which is useful to provide the context.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at element-hq/synapse#18876, I don't see how the user will know what limit they went over though.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the context of Synapse and the media upload limits feature, I imagined it could be configured with context like this:

media_upload_limits:
- time_period: 24h
  max_size: 100M
  msc4335_info_uri: https://example.com/quota?type=daily
  msc4335_soft_limit: true
  msc4335_increase_uri: https://example.com/increase-quota?type=daily
- time_period: 28d
  max_size: 500M
  msc4335_info_uri: https://example.com/quota?type=monthly
  msc4335_soft_limit: true
  msc4335_increase_uri: https://example.com/increase-quota?type=monthly

Which would result in error response such as:

{
  "errcode": "M_USER_LIMIT_EXCEEDED",
  "error": "User has exceeded their upload limit",
  "info_uri": "https://example.com/quota?type=daily",
  "soft_limit": true,
  "increase_uri": "https://example.com/increase-quota?type=daily"
}

The web page at the other end can then serve up content that is specific to the limit exceeded.

Generalising this, the homeserver implementation and/or operator could also do things like:

In order to implement the soft limit and option to upgrade, a Synapse operator would need to make use the get_media_upload_limits_for_user from the module API to allow certain users to have different media upload limits.

@clokep is it that you prefer the approach described at https://github.com/matrix-org/matrix-spec-proposals/blob/hughns/user-limit-exceeded/proposals/4335-user-limit-exceeded.md#add-structured-error-information? If so then could you add a "vote" or preference to that alternative instead?

If not, then I feel like I am missing something.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, that sounds fine. I think that's not clear from the text that a different URI per resource is expected. And the matching implementation doesn't do this.


The homeserver can choose to provide localised and personalised content on the `info_uri`, `soft_limit` 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`] error code could be
expanded to cover user-specific limits. However, this code is currently used for server-wide
resource constraints, and overloading it could create confusion about whether the limit applies
to the user specifically or the server generally.

**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.

## 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