diff --git a/templates/zerver/api/mute-topics.md b/templates/zerver/api/mute-topics.md
new file mode 100644
index 0000000000..44226ce82a
--- /dev/null
+++ b/templates/zerver/api/mute-topics.md
@@ -0,0 +1,61 @@
+# Topic muting
+
+This endpoint mutes/unmutes a topic within a stream that the current
+user is subscribed to. Muted topics are displayed faded in the Zulip
+UI, and are not included in the user's unread count totals.
+
+`PATCH {{ api_url }}/v1/users/me/subscriptions/muted_topics`
+
+## Usage examples
+
+
+
+
+
+
+
+```
+curl -X PATCH {{ api_url }}/v1/users/me/subscriptions/muted_topics \
+ -u BOT_EMAIL_ADDRESS:BOT_API_KEY \
+ -d "stream=Verona"
+ -d "topic=dinner"
+ -d "op=add"
+```
+
+
+
+
+
+{generate_code_example(python)|/users/me/subscriptions/muted_topics:patch|example}
+
+
+
+
+
+
+
+## Arguments
+
+{generate_api_arguments_table|zulip.yaml|/users/me/subscriptions/muted_topics:patch}
+
+## Response
+
+#### Example response
+
+A typical successful JSON response may look like:
+
+{generate_code_example|/users/me/subscriptions/muted_topics:patch|fixture(200)}
+
+
+An example JSON response for when an `add` operation is requested for a topic
+that has already been muted:
+
+{generate_code_example|/users/me/subscriptions/muted_topics:patch|fixture(400_topic_already_muted)}
+
+An example JSON response for when a `remove` operation is requested for a
+topic that had not been previously muted:
+
+{generate_code_example|/users/me/subscriptions/muted_topics:patch|fixture(400_topic_not_muted)}
diff --git a/templates/zerver/help/include/rest-endpoints.md b/templates/zerver/help/include/rest-endpoints.md
index a6c861c065..8ce18de740 100644
--- a/templates/zerver/help/include/rest-endpoints.md
+++ b/templates/zerver/help/include/rest-endpoints.md
@@ -13,6 +13,7 @@
* [Add subscriptions](/api/add-subscriptions)
* [Remove subscriptions](/api/remove-subscriptions)
* [Get topics in a stream](/api/get-stream-topics)
+* [Topic muting](/api/mute-topics)
* [Create a stream](/api/create-stream)
* [Get stream ID](/api/get-stream-id)
diff --git a/zerver/lib/api_test_helpers.py b/zerver/lib/api_test_helpers.py
index b585c6d65f..c9e244eb14 100644
--- a/zerver/lib/api_test_helpers.py
+++ b/zerver/lib/api_test_helpers.py
@@ -267,6 +267,50 @@ def remove_subscriptions(client):
validate_against_openapi_schema(result, '/users/me/subscriptions',
'delete', '200')
+def toggle_mute_topic(client):
+ # type: (Client) -> None
+
+ # Send a test message
+ message = {
+ 'type': 'stream',
+ 'to': 'Denmark',
+ 'subject': 'boat party'
+ }
+ client.call_endpoint(
+ url='messages',
+ method='POST',
+ request=message
+ )
+
+ # {code_example|start}
+ # Mute the topic "boat party" in the stream "Denmark"
+ request = {
+ 'stream': 'Denmark',
+ 'topic': 'boat party',
+ 'op': 'add'
+ }
+ result = client.mute_topic(request)
+ # {code_example|end}
+
+ validate_against_openapi_schema(result,
+ '/users/me/subscriptions/muted_topics',
+ 'patch', '200')
+
+ # {code_example|start}
+ # Unmute the topic "boat party" in the stream "Denmark"
+ request = {
+ 'stream': 'Denmark',
+ 'topic': 'boat party',
+ 'op': 'remove'
+ }
+
+ result = client.mute_topic(request)
+ # {code_example|end}
+
+ validate_against_openapi_schema(result,
+ '/users/me/subscriptions/muted_topics',
+ 'patch', '200')
+
def render_message(client):
# type: (Client) -> None
@@ -513,6 +557,7 @@ TEST_FUNCTIONS = {
'get-profile': get_profile,
'add-subscriptions': add_subscriptions,
'/users/me/subscriptions:delete': remove_subscriptions,
+ '/users/me/subscriptions/muted_topics:patch': toggle_mute_topic,
'/users:get': get_members,
'/realm/filters:post': add_realm_filter,
'/register:post': register_queue,
@@ -600,6 +645,7 @@ def test_streams(client, nonadmin_client):
get_streams(client)
get_subscribers(client)
remove_subscriptions(client)
+ toggle_mute_topic(client)
get_stream_topics(client, 1)
test_user_not_authorized_error(nonadmin_client)
diff --git a/zerver/openapi/zulip.yaml b/zerver/openapi/zulip.yaml
index 59acfd49d3..a39be55d64 100644
--- a/zerver/openapi/zulip.yaml
+++ b/zerver/openapi/zulip.yaml
@@ -790,6 +790,71 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/NonExistingStreamError'
+ /users/me/subscriptions/muted_topics:
+ patch:
+ description: Toggle muting for a specific topic the user is subscribed
+ to.
+ parameters:
+ - name: stream
+ in: query_function
+ description: The name of the stream in which to mute the topic.
+ schema:
+ type: string
+ example: Verona
+ required: true
+ - name: topic
+ in: query
+ description: The topic to (un)mute. Note that the request will succeed
+ regardless of whether any messages have been sent to the
+ specified topic.
+ schema:
+ type: string
+ example: dinner
+ required: true
+ - name: op
+ in: query
+ description: Whether to mute (`add`) or unmute (`remove`) the provided
+ topic.
+ schema:
+ type: string
+ enum:
+ - add
+ - remove
+ example: add
+ required: true
+ security:
+ - basicAuth: []
+ responses:
+ '200':
+ description: Success.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/JsonSuccess'
+ '400_topic_already_muted':
+ description: Bad request.
+ content:
+ application/json:
+ schema:
+ allOf:
+ - $ref: '#/components/schemas/JsonError'
+ - example:
+ {
+ "msg": "Topic already muted",
+ "result": "error"
+ }
+ '400_topic_not_muted':
+ description: Bad request.
+ content:
+ application/json:
+ schema:
+ allOf:
+ - $ref: '#/components/schemas/JsonError'
+ - example:
+ {
+ "msg": "Topic is not there in the muted_topics list",
+ "result": "error"
+ }
/realm/filters:
post:
description: Establish patterns in the messages that should be