diff --git a/docs/API/differences_in_mastoapi_responses.md b/docs/API/differences_in_mastoapi_responses.md
index 1e932d908..c6d822bfc 100644
--- a/docs/API/differences_in_mastoapi_responses.md
+++ b/docs/API/differences_in_mastoapi_responses.md
@@ -14,7 +14,7 @@ Adding the parameter `reply_visibility` to the public and home timelines queries
 
 ## Statuses
 
-- `visibility`: has an additional possible value `list`
+- `visibility`: has additional possible values `list` and `local` (for local-only statuses)
 
 Has these additional fields under the `pleroma` object:
 
@@ -28,7 +28,6 @@ Has these additional fields under the `pleroma` object:
 - `thread_muted`: true if the thread the post belongs to is muted
 - `emoji_reactions`: A list with emoji / reaction maps. The format is `{name: "☕", count: 1, me: true}`. Contains no information about the reacting users, for that use the `/statuses/:id/reactions` endpoint.
 - `parent_visible`: If the parent of this post is visible to the user or not.
-- `local_only`: true for local-only, non-federated posts.
 
 ## Media Attachments
 
@@ -152,10 +151,9 @@ Additional parameters can be added to the JSON body/Form data:
 - `preview`: boolean, if set to `true` the post won't be actually posted, but the status entitiy would still be rendered back. This could be useful for previewing rich text/custom emoji, for example.
 - `content_type`: string, contain the MIME type of the status, it is transformed into HTML by the backend. You can get the list of the supported MIME types with the nodeinfo endpoint.
 - `to`: A list of nicknames (like `lain@soykaf.club` or `lain` on the local server) that will be used to determine who is going to be addressed by this post. Using this will disable the implicit addressing by mentioned names in the `status` body, only the people in the `to` list will be addressed. The normal rules for for post visibility are not affected by this and will still apply.
-- `visibility`: string, besides standard MastoAPI values (`direct`, `private`, `unlisted` or `public`) it can be used to address a List by setting it to `list:LIST_ID`.
+- `visibility`: string, besides standard MastoAPI values (`direct`, `private`, `unlisted`, `local` or `public`) it can be used to address a List by setting it to `list:LIST_ID`.
 - `expires_in`: The number of seconds the posted activity should expire in. When a posted activity expires it will be deleted from the server, and a delete request for it will be federated. This needs to be longer than an hour.
 - `in_reply_to_conversation_id`: Will reply to a given conversation, addressing only the people who are part of the recipient set of that conversation. Sets the visibility to `direct`.
-- `local_only`: boolean, if set to `true` the post won't be federated.
 
 ## GET `/api/v1/statuses`
 
diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex
index 648cfb623..553834da0 100644
--- a/lib/pleroma/activity.ex
+++ b/lib/pleroma/activity.ex
@@ -19,8 +19,6 @@ defmodule Pleroma.Activity do
   import Ecto.Changeset
   import Ecto.Query
 
-  require Pleroma.Constants
-
   @type t :: %__MODULE__{}
   @type actor :: String.t()
 
@@ -358,12 +356,4 @@ def pinned_by_actor?(%Activity{} = activity) do
     actor = user_actor(activity)
     activity.id in actor.pinned_activities
   end
-
-  def local_only?(activity) do
-    recipients = Enum.concat(activity.data["to"], Map.get(activity.data, "cc", []))
-    public = Pleroma.Constants.as_public()
-    local = Pleroma.Constants.as_local_public()
-
-    Enum.member?(recipients, local) and not Enum.member?(recipients, public)
-  end
 end
diff --git a/lib/pleroma/web/activity_pub/builder.ex b/lib/pleroma/web/activity_pub/builder.ex
index c9200a3f0..e99f6fd83 100644
--- a/lib/pleroma/web/activity_pub/builder.ex
+++ b/lib/pleroma/web/activity_pub/builder.ex
@@ -222,7 +222,7 @@ def announce(actor, object, options \\ []) do
         actor.ap_id == Relay.ap_id() ->
           [actor.follower_address]
 
-        public? and Pleroma.Activity.local_only?(object) ->
+        public? and Visibility.is_local_public?(object) ->
           [actor.follower_address, object.data["actor"], Pleroma.Constants.as_local_public()]
 
         public? ->
diff --git a/lib/pleroma/web/activity_pub/pipeline.ex b/lib/pleroma/web/activity_pub/pipeline.ex
index 559c8387e..98c32a42b 100644
--- a/lib/pleroma/web/activity_pub/pipeline.ex
+++ b/lib/pleroma/web/activity_pub/pipeline.ex
@@ -11,6 +11,7 @@ defmodule Pleroma.Web.ActivityPub.Pipeline do
   alias Pleroma.Web.ActivityPub.MRF
   alias Pleroma.Web.ActivityPub.ObjectValidator
   alias Pleroma.Web.ActivityPub.SideEffects
+  alias Pleroma.Web.ActivityPub.Visibility
   alias Pleroma.Web.Federator
 
   @spec common_pipeline(map(), keyword()) ::
@@ -55,7 +56,7 @@ defp maybe_federate(%Activity{} = activity, meta) do
     with {:ok, local} <- Keyword.fetch(meta, :local) do
       do_not_federate = meta[:do_not_federate] || !Config.get([:instance, :federating])
 
-      if !do_not_federate and local and not Activity.local_only?(activity) do
+      if !do_not_federate and local and not Visibility.is_local_public?(activity) do
         activity =
           if object = Keyword.get(meta, :object_data) do
             %{activity | data: Map.put(activity.data, "object", object)}
diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex
index faf3bea00..46002bec2 100644
--- a/lib/pleroma/web/activity_pub/utils.ex
+++ b/lib/pleroma/web/activity_pub/utils.ex
@@ -176,7 +176,7 @@ def maybe_federate(%Activity{local: true, data: %{"type" => type}} = activity) d
 
     with true <- Config.get!([:instance, :federating]),
          true <- type != "Block" || outgoing_blocks,
-         false <- Activity.local_only?(activity) do
+         false <- Visibility.is_local_public?(activity) do
       Pleroma.Web.Federator.publish(activity)
     end
 
diff --git a/lib/pleroma/web/activity_pub/visibility.ex b/lib/pleroma/web/activity_pub/visibility.ex
index b3b23a38b..2cb5a2bd0 100644
--- a/lib/pleroma/web/activity_pub/visibility.ex
+++ b/lib/pleroma/web/activity_pub/visibility.ex
@@ -23,6 +23,14 @@ def is_public?(data) do
       Utils.label_in_message?(Pleroma.Constants.as_local_public(), data)
   end
 
+  def is_local_public?(%Object{data: data}), do: is_local_public?(data)
+  def is_local_public?(%Activity{data: data}), do: is_local_public?(data)
+
+  def is_local_public?(data) do
+    Utils.label_in_message?(Pleroma.Constants.as_local_public(), data) and
+      not Utils.label_in_message?(Pleroma.Constants.as_public(), data)
+  end
+
   def is_private?(activity) do
     with false <- is_public?(activity),
          %User{follower_address: follower_address} <-
@@ -118,6 +126,9 @@ def get_visibility(object) do
       Pleroma.Constants.as_public() in cc ->
         "unlisted"
 
+      Pleroma.Constants.as_local_public() in to ->
+        "local"
+
       # this should use the sql for the object's activity
       Enum.any?(to, &String.contains?(&1, "/followers")) ->
         "private"
diff --git a/lib/pleroma/web/api_spec/operations/status_operation.ex b/lib/pleroma/web/api_spec/operations/status_operation.ex
index e989e4f5f..d7ebde6f6 100644
--- a/lib/pleroma/web/api_spec/operations/status_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/status_operation.ex
@@ -475,10 +475,6 @@ defp create_request do
           type: :string,
           description:
             "Will reply to a given conversation, addressing only the people who are part of the recipient set of that conversation. Sets the visibility to `direct`."
-        },
-        local_only: %Schema{
-          type: :boolean,
-          description: "Post the status as local only"
         }
       },
       example: %{
diff --git a/lib/pleroma/web/api_spec/schemas/visibility_scope.ex b/lib/pleroma/web/api_spec/schemas/visibility_scope.ex
index 831734e27..633269a92 100644
--- a/lib/pleroma/web/api_spec/schemas/visibility_scope.ex
+++ b/lib/pleroma/web/api_spec/schemas/visibility_scope.ex
@@ -9,6 +9,6 @@ defmodule Pleroma.Web.ApiSpec.Schemas.VisibilityScope do
     title: "VisibilityScope",
     description: "Status visibility",
     type: :string,
-    enum: ["public", "unlisted", "private", "direct", "list"]
+    enum: ["public", "unlisted", "local", "private", "direct", "list"]
   })
 end
diff --git a/lib/pleroma/web/common_api.ex b/lib/pleroma/web/common_api.ex
index 4df37b695..31d9ea677 100644
--- a/lib/pleroma/web/common_api.ex
+++ b/lib/pleroma/web/common_api.ex
@@ -359,7 +359,7 @@ def public_announce?(object, _) do
   def get_visibility(_, _, %Participation{}), do: {"direct", "direct"}
 
   def get_visibility(%{visibility: visibility}, in_reply_to, _)
-      when visibility in ~w{public unlisted private direct},
+      when visibility in ~w{public local unlisted private direct},
       do: {visibility, get_replied_to_visibility(in_reply_to)}
 
   def get_visibility(%{visibility: "list:" <> list_id}, in_reply_to, _) do
diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex
index abf6c40d5..ae133b54f 100644
--- a/lib/pleroma/web/common_api/utils.ex
+++ b/lib/pleroma/web/common_api/utils.ex
@@ -65,8 +65,14 @@ def get_to_and_cc(%{in_reply_to_conversation: %Participation{} = participation})
     {Enum.map(participation.recipients, & &1.ap_id), []}
   end
 
-  def get_to_and_cc(%{visibility: "public"} = draft) do
-    to = [public_uri(draft) | draft.mentions]
+  def get_to_and_cc(%{visibility: visibility} = draft) when visibility in ["public", "local"] do
+
+    to =
+      case visibility do
+        "public" -> [Pleroma.Constants.as_public() | draft.mentions]
+        "local" -> [Pleroma.Constants.as_local_public() | draft.mentions]
+      end
+
     cc = [draft.user.follower_address]
 
     if draft.in_reply_to do
@@ -78,7 +84,7 @@ def get_to_and_cc(%{visibility: "public"} = draft) do
 
   def get_to_and_cc(%{visibility: "unlisted"} = draft) do
     to = [draft.user.follower_address | draft.mentions]
-    cc = [public_uri(draft)]
+    cc = [Pleroma.Constants.as_public()]
 
     if draft.in_reply_to do
       {Enum.uniq([draft.in_reply_to.data["actor"] | to]), cc}
@@ -103,9 +109,6 @@ def get_to_and_cc(%{visibility: "direct"} = draft) do
 
   def get_to_and_cc(%{visibility: {:list, _}, mentions: mentions}), do: {mentions, []}
 
-  defp public_uri(%{params: %{local_only: true}}), do: Pleroma.Constants.as_local_public()
-  defp public_uri(_), do: Pleroma.Constants.as_public()
-
   def get_addressed_users(_, to) when is_list(to) do
     User.get_ap_ids_by_nicknames(to)
   end
diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex
index 0fc78972e..435bcde15 100644
--- a/lib/pleroma/web/mastodon_api/views/status_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/status_view.ex
@@ -368,8 +368,7 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity}
         direct_conversation_id: direct_conversation_id,
         thread_muted: thread_muted?,
         emoji_reactions: emoji_reactions,
-        parent_visible: visible_for_user?(reply_to, opts[:for]),
-        local_only: Activity.local_only?(activity)
+        parent_visible: visible_for_user?(reply_to, opts[:for])
       }
     }
   end
diff --git a/test/pleroma/web/common_api_test.exs b/test/pleroma/web/common_api_test.exs
index e1dddd21a..598ff87de 100644
--- a/test/pleroma/web/common_api_test.exs
+++ b/test/pleroma/web/common_api_test.exs
@@ -1256,16 +1256,16 @@ test "fallback" do
     end
   end
 
-  describe "with `local_only` enabled" do
+  describe "with `local` visibility" do
     setup do: clear_config([:instance, :federating], true)
 
     test "post" do
       user = insert(:user)
 
       with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
-        {:ok, activity} = CommonAPI.post(user, %{status: "#2hu #2HU", local_only: true})
+        {:ok, activity} = CommonAPI.post(user, %{status: "#2hu #2HU", visibility: "local"})
 
-        assert Activity.local_only?(activity)
+        assert Visibility.is_local_public?(activity)
         assert_not_called(Pleroma.Web.Federator.publish(activity))
       end
     end
@@ -1274,13 +1274,13 @@ test "delete" do
       user = insert(:user)
 
       {:ok, %Activity{id: activity_id}} =
-        CommonAPI.post(user, %{status: "#2hu #2HU", local_only: true})
+        CommonAPI.post(user, %{status: "#2hu #2HU", visibility: "local"})
 
       with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
         assert {:ok, %Activity{data: %{"deleted_activity_id" => ^activity_id}} = activity} =
                  CommonAPI.delete(activity_id, user)
 
-        assert Activity.local_only?(activity)
+        assert Visibility.is_local_public?(activity)
         assert_not_called(Pleroma.Web.Federator.publish(activity))
       end
     end
@@ -1290,13 +1290,13 @@ test "repeat" do
       other_user = insert(:user)
 
       {:ok, %Activity{id: activity_id}} =
-        CommonAPI.post(other_user, %{status: "cofe", local_only: true})
+        CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
 
       with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
         assert {:ok, %Activity{data: %{"type" => "Announce"}} = activity} =
                  CommonAPI.repeat(activity_id, user)
 
-        assert Activity.local_only?(activity)
+        assert Visibility.is_local_public?(activity)
         refute called(Pleroma.Web.Federator.publish(activity))
       end
     end
@@ -1306,7 +1306,7 @@ test "unrepeat" do
       other_user = insert(:user)
 
       {:ok, %Activity{id: activity_id}} =
-        CommonAPI.post(other_user, %{status: "cofe", local_only: true})
+        CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
 
       assert {:ok, _} = CommonAPI.repeat(activity_id, user)
 
@@ -1314,7 +1314,7 @@ test "unrepeat" do
         assert {:ok, %Activity{data: %{"type" => "Undo"}} = activity} =
                  CommonAPI.unrepeat(activity_id, user)
 
-        assert Activity.local_only?(activity)
+        assert Visibility.is_local_public?(activity)
         refute called(Pleroma.Web.Federator.publish(activity))
       end
     end
@@ -1323,13 +1323,13 @@ test "favorite" do
       user = insert(:user)
       other_user = insert(:user)
 
-      {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe", local_only: true})
+      {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
 
       with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
         assert {:ok, %Activity{data: %{"type" => "Like"}} = activity} =
                  CommonAPI.favorite(user, activity.id)
 
-        assert Activity.local_only?(activity)
+        assert Visibility.is_local_public?(activity)
         refute called(Pleroma.Web.Federator.publish(activity))
       end
     end
@@ -1338,13 +1338,13 @@ test "unfavorite" do
       user = insert(:user)
       other_user = insert(:user)
 
-      {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe", local_only: true})
+      {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
 
       {:ok, %Activity{}} = CommonAPI.favorite(user, activity.id)
 
       with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
         assert {:ok, activity} = CommonAPI.unfavorite(activity.id, user)
-        assert Activity.local_only?(activity)
+        assert Visibility.is_local_public?(activity)
         refute called(Pleroma.Web.Federator.publish(activity))
       end
     end
@@ -1352,13 +1352,13 @@ test "unfavorite" do
     test "react_with_emoji" do
       user = insert(:user)
       other_user = insert(:user)
-      {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe", local_only: true})
+      {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
 
       with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
         assert {:ok, %Activity{data: %{"type" => "EmojiReact"}} = activity} =
                  CommonAPI.react_with_emoji(activity.id, user, "👍")
 
-        assert Activity.local_only?(activity)
+        assert Visibility.is_local_public?(activity)
         refute called(Pleroma.Web.Federator.publish(activity))
       end
     end
@@ -1366,7 +1366,7 @@ test "react_with_emoji" do
     test "unreact_with_emoji" do
       user = insert(:user)
       other_user = insert(:user)
-      {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe", local_only: true})
+      {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
 
       {:ok, _reaction} = CommonAPI.react_with_emoji(activity.id, user, "👍")
 
@@ -1374,7 +1374,7 @@ test "unreact_with_emoji" do
         assert {:ok, %Activity{data: %{"type" => "Undo"}} = activity} =
                  CommonAPI.unreact_with_emoji(activity.id, user, "👍")
 
-        assert Activity.local_only?(activity)
+        assert Visibility.is_local_public?(activity)
         refute called(Pleroma.Web.Federator.publish(activity))
       end
     end
diff --git a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs
index ddddd0ea0..d95200f99 100644
--- a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs
+++ b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs
@@ -1749,12 +1749,12 @@ test "posting a local only status" do
       |> put_req_header("content-type", "application/json")
       |> post("/api/v1/statuses", %{
         "status" => "cofe",
-        "local_only" => "true"
+        "visibility" => "local"
       })
 
     local = Pleroma.Constants.as_local_public()
 
-    assert %{"content" => "cofe", "id" => id, "pleroma" => %{"local_only" => true}} =
+    assert %{"content" => "cofe", "id" => id, "visibility" => "local"} =
              json_response(conn_one, 200)
 
     assert %Activity{id: ^id, data: %{"to" => [^local]}} = Activity.get_by_id(id)
diff --git a/test/pleroma/web/mastodon_api/views/status_view_test.exs b/test/pleroma/web/mastodon_api/views/status_view_test.exs
index 03b0cdf15..70d829979 100644
--- a/test/pleroma/web/mastodon_api/views/status_view_test.exs
+++ b/test/pleroma/web/mastodon_api/views/status_view_test.exs
@@ -245,8 +245,7 @@ test "a note activity" do
         direct_conversation_id: nil,
         thread_muted: false,
         emoji_reactions: [],
-        parent_visible: false,
-        local_only: false
+        parent_visible: false
       }
     }