diff --git a/Dockerfile b/Dockerfile
index 029e6a58e..8685ad386 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,7 +1,7 @@
 FROM bitwalker/alpine-elixir:latest
 
 RUN apk add inotify-tools postgresql-client yarn
-RUN apk add --no-cache make gcc libc-dev
+RUN apk add --no-cache make gcc libc-dev argon2
 
 RUN mix local.hex --force && mix local.rebar --force
 
diff --git a/config/config.exs b/config/config.exs
index 1f26daec3..c83cac04c 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -64,7 +64,7 @@ config :geolix,
 config :arc,
   storage: Arc.Storage.Local
 
-config :phoenix, :format_encoders, json: Jason
+config :phoenix, :format_encoders, json: Jason, "activity-json": Jason
 
 config :mobilizon, Mobilizon.Service.Geospatial.Nominatim,
   endpoint:
diff --git a/lib/eventos.ex b/lib/mobilizon.ex
similarity index 100%
rename from lib/eventos.ex
rename to lib/mobilizon.ex
diff --git a/lib/eventos_web.ex b/lib/mobilizon_web.ex
similarity index 100%
rename from lib/eventos_web.ex
rename to lib/mobilizon_web.ex
diff --git a/lib/mobilizon_web/controllers/activity_pub_controller.ex b/lib/mobilizon_web/controllers/activity_pub_controller.ex
index 4ffae9395..e0898745a 100644
--- a/lib/mobilizon_web/controllers/activity_pub_controller.ex
+++ b/lib/mobilizon_web/controllers/activity_pub_controller.ex
@@ -16,74 +16,6 @@ defmodule MobilizonWeb.ActivityPubController do
 
   action_fallback(:errors)
 
-  @doc """
-  Renders an Actor ActivityPub's representation
-  """
-  @spec actor(Plug.Conn.t(), String.t()) :: Plug.Conn.t()
-  def actor(conn, %{"name" => name}) do
-    with {status, %Actor{} = actor} when status in [:ok, :commit] <-
-           Actors.get_cached_local_actor_by_name(name) do
-      conn
-      |> put_resp_header("content-type", "application/activity+json")
-      |> json(ActorView.render("actor.json", %{actor: actor}))
-    else
-      {:ignore, _} ->
-        {:error, :not_found}
-    end
-  end
-
-  @doc """
-  Renders an Event ActivityPub's representation
-  """
-  @spec event(Plug.Conn.t(), map()) :: Plug.Conn.t()
-  def event(conn, %{"uuid" => uuid}) do
-    with {status, %Event{} = event} when status in [:ok, :commit] <-
-           Events.get_cached_event_full_by_uuid(uuid),
-         true <- event.visibility in [:public, :unlisted] do
-      conn
-      |> put_resp_header("content-type", "application/activity+json")
-      |> json(
-        ObjectView.render(
-          "event.json",
-          %{
-            event:
-              event
-              |> Utils.make_event_data()
-          }
-        )
-      )
-    else
-      {:ignore, _} ->
-        {:error, :not_found}
-    end
-  end
-
-  @doc """
-  Renders a Comment ActivityPub's representation
-  """
-  @spec comment(Plug.Conn.t(), map()) :: Plug.Conn.t()
-  def comment(conn, %{"uuid" => uuid}) do
-    with {status, %Comment{} = comment} when status in [:ok, :commit] <-
-           Events.get_cached_comment_full_by_uuid(uuid),
-         true <- comment.visibility in [:public, :unlisted] do
-      conn
-      |> put_resp_header("content-type", "application/activity+json")
-      |> json(
-        ObjectView.render(
-          "comment.json",
-          %{
-            comment:
-              comment
-              |> Utils.make_comment_data()
-          }
-        )
-      )
-    else
-      _ ->
-        {:error, :not_found}
-    end
-  end
-
   def following(conn, %{"name" => name, "page" => page}) do
     with {page, ""} = Integer.parse(page),
          %Actor{} = actor <- Actors.get_local_actor_by_name_with_everything(name) do
diff --git a/lib/mobilizon_web/controllers/fallback_controller.ex b/lib/mobilizon_web/controllers/fallback_controller.ex
index ec74eadba..aea620e1b 100644
--- a/lib/mobilizon_web/controllers/fallback_controller.ex
+++ b/lib/mobilizon_web/controllers/fallback_controller.ex
@@ -10,6 +10,6 @@ defmodule MobilizonWeb.FallbackController do
     conn
     |> put_status(:not_found)
     |> put_view(MobilizonWeb.ErrorView)
-    |> render("404.html")
+    |> render(:"404")
   end
 end
diff --git a/lib/mobilizon_web/controllers/page_controller.ex b/lib/mobilizon_web/controllers/page_controller.ex
index 5bb761542..9c479edff 100644
--- a/lib/mobilizon_web/controllers/page_controller.ex
+++ b/lib/mobilizon_web/controllers/page_controller.ex
@@ -10,69 +10,35 @@ defmodule MobilizonWeb.PageController do
 
   action_fallback(MobilizonWeb.FallbackController)
 
-  def index(conn, _params) do
-    render(conn, "app.html")
-  end
+  def index(conn, _params), do: render(conn, :index)
 
   def actor(conn, %{"name" => name}) do
-    case get_format(conn) do
-      "html" ->
-        with {status, %Actor{} = actor} when status in [:ok, :commit] <-
-               Actors.get_cached_local_actor_by_name(name) do
-          render_with_meta(conn, actor)
-        else
-          _ -> {:error, :not_found}
-        end
-
-      # "activity-json" matches "application/activity+json" inside our config
-      "activity-json" ->
-        MobilizonWeb.ActivityPubController.call(conn, :actor)
-
-      _ ->
-        {:error, :not_found}
-    end
+    {status, actor} = Actors.get_cached_local_actor_by_name(name)
+    render_or_error(conn, &ok_status?/2, status, :actor, actor)
   end
 
   def event(conn, %{"uuid" => uuid}) do
-    case get_format(conn) do
-      "html" ->
-        with {status, %Event{} = event} when status in [:ok, :commit] <-
-               Events.get_cached_event_full_by_uuid(uuid),
-             true <- event.visibility in [:public, :unlisted] do
-          render_with_meta(conn, event)
-        else
-          _ -> {:error, :not_found}
-        end
-
-      "activity-json" ->
-        MobilizonWeb.ActivityPubController.call(conn, :event)
-
-      _ ->
-        {:error, :not_found}
-    end
+    {status, event} = Events.get_cached_event_full_by_uuid(uuid)
+    render_or_error(conn, &ok_status_and_is_visible?/2, status, :event, event)
   end
 
   def comment(conn, %{"uuid" => uuid}) do
-    case get_format(conn) do
-      "html" ->
-        with {status, %Comment{} = comment} when status in [:ok, :commit] <-
-               Events.get_cached_comment_full_by_uuid(uuid),
-             true <- comment.visibility in [:public, :unlisted] do
-          render_with_meta(conn, comment)
-        else
-          _ -> {:error, :not_found}
-        end
+    {status, comment} = Events.get_cached_comment_full_by_uuid(uuid)
+    render_or_error(conn, &ok_status_and_is_visible?/2, status, :comment, comment)
+  end
 
-      "activity-json" ->
-        MobilizonWeb.ActivityPubController.call(conn, :comment)
-
-      _ ->
-        {:error, :not_found}
+  defp render_or_error(conn, check_fn, status, object_type, object) do
+    if check_fn.(status, object) do
+      render(conn, object_type, object: object)
+    else
+      {:error, :not_found}
     end
   end
 
-  # Inject OpenGraph information
-  defp render_with_meta(conn, object) do
-    render(conn, "app.html", object: object)
-  end
+  defp is_visible?(%{visibility: v}), do: v in [:public, :unlisted]
+
+  defp ok_status?(status), do: status in [:ok, :commit]
+  defp ok_status?(status, _), do: ok_status?(status)
+
+  defp ok_status_and_is_visible?(status, o), do: ok_status?(status) and is_visible?(o)
 end
diff --git a/lib/mobilizon_web/views/activity_pub/object_view.ex b/lib/mobilizon_web/views/activity_pub/object_view.ex
index 9e7de95b8..9a1da8fca 100644
--- a/lib/mobilizon_web/views/activity_pub/object_view.ex
+++ b/lib/mobilizon_web/views/activity_pub/object_view.ex
@@ -3,45 +3,6 @@ defmodule MobilizonWeb.ActivityPub.ObjectView do
   alias Mobilizon.Service.ActivityPub.Utils
   alias Mobilizon.Activity
 
-  def render("event.json", %{event: event}) do
-    {:ok, html, []} = Earmark.as_html(event["summary"])
-
-    event = %{
-      "type" => "Event",
-      "attributedTo" => event["actor"],
-      "id" => event["id"],
-      "name" => event["title"],
-      "category" => event["category"],
-      "content" => html,
-      "source" => %{
-        "content" => event["summary"],
-        "mediaType" => "text/markdown"
-      },
-      "mediaType" => "text/html",
-      "published" => event["publish_at"],
-      "updated" => event["updated_at"]
-    }
-
-    Map.merge(event, Utils.make_json_ld_header())
-  end
-
-  def render("comment.json", %{comment: comment}) do
-    comment = %{
-      "actor" => comment["actor"],
-      "uuid" => comment["uuid"],
-      # The activity should have attributedTo, not the comment itself
-      #      "attributedTo" => comment.attributed_to,
-      "type" => "Note",
-      "id" => comment["id"],
-      "content" => comment["content"],
-      "mediaType" => "text/html"
-      # "published" => Timex.format!(comment.inserted_at, "{ISO:Extended}"),
-      # "updated" => Timex.format!(comment.updated_at, "{ISO:Extended}")
-    }
-
-    Map.merge(comment, Utils.make_json_ld_header())
-  end
-
   def render("activity.json", %{activity: %Activity{local: local, data: data} = activity}) do
     %{
       "id" => data["id"],
diff --git a/lib/mobilizon_web/views/error_view.ex b/lib/mobilizon_web/views/error_view.ex
index c4d6b3ce4..3a252a8bf 100644
--- a/lib/mobilizon_web/views/error_view.ex
+++ b/lib/mobilizon_web/views/error_view.ex
@@ -8,6 +8,10 @@ defmodule MobilizonWeb.ErrorView do
     "Page not found"
   end
 
+  def render("404.json", _assigns) do
+    %{msg: "Resource not found"}
+  end
+
   def render("invalid_request.json", _assigns) do
     %{errors: "Invalid request"}
   end
diff --git a/lib/mobilizon_web/views/page_view.ex b/lib/mobilizon_web/views/page_view.ex
index ea88b06af..0166680fd 100644
--- a/lib/mobilizon_web/views/page_view.ex
+++ b/lib/mobilizon_web/views/page_view.ex
@@ -3,4 +3,83 @@ defmodule MobilizonWeb.PageView do
   View for our webapp
   """
   use MobilizonWeb, :view
+  alias Mobilizon.Actors.Actor
+  alias Mobilizon.Service.ActivityPub.Utils
+
+  def render("actor.activity-json", %{conn: %{assigns: %{object: actor}}}) do
+    public_key = Mobilizon.Service.ActivityPub.Utils.pem_to_public_key_pem(actor.keys)
+
+    %{
+      "id" => Actor.build_url(actor.preferred_username, :page),
+      "type" => "Person",
+      "following" => Actor.build_url(actor.preferred_username, :following),
+      "followers" => Actor.build_url(actor.preferred_username, :followers),
+      "inbox" => Actor.build_url(actor.preferred_username, :inbox),
+      "outbox" => Actor.build_url(actor.preferred_username, :outbox),
+      "preferredUsername" => actor.preferred_username,
+      "name" => actor.name,
+      "summary" => actor.summary,
+      "url" => actor.url,
+      "manuallyApprovesFollowers" => actor.manually_approves_followers,
+      "publicKey" => %{
+        "id" => "#{actor.url}#main-key",
+        "owner" => actor.url,
+        "publicKeyPem" => public_key
+      },
+      # TODO : Make have actors have an uuid
+      # "uuid" => actor.uuid
+      "endpoints" => %{
+        "sharedInbox" => actor.shared_inbox_url
+      }
+      #      "icon" => %{
+      #        "type" => "Image",
+      #        "url" => User.avatar_url(actor)
+      #      },
+      #      "image" => %{
+      #        "type" => "Image",
+      #        "url" => User.banner_url(actor)
+      #      }
+    }
+    |> Map.merge(Utils.make_json_ld_header())
+  end
+
+  def render("event.activity-json", %{conn: %{assigns: %{object: event}}}) do
+    event = Utils.make_event_data(event)
+    {:ok, html, []} = Earmark.as_html(event["summary"])
+
+    %{
+      "type" => "Event",
+      "attributedTo" => event["actor"],
+      "id" => event["id"],
+      "name" => event["title"],
+      "category" => event["category"],
+      "content" => html,
+      "source" => %{
+        "content" => event["summary"],
+        "mediaType" => "text/markdown"
+      },
+      "mediaType" => "text/html",
+      "published" => event["publish_at"],
+      "updated" => event["updated_at"]
+    }
+    |> Map.merge(Utils.make_json_ld_header())
+  end
+
+  def render("comment.activity-json", %{conn: %{assigns: %{object: comment}}}) do
+    comment = Utils.make_comment_data(comment)
+
+    %{
+      "actor" => comment["actor"],
+      "uuid" => comment["uuid"],
+      # The activity should have attributedTo, not the comment itself
+      #      "attributedTo" => comment.attributed_to,
+      "type" => "Note",
+      "id" => comment["id"],
+      "content" => comment["content"],
+      "mediaType" => "text/html"
+      # "published" => Timex.format!(comment.inserted_at, "{ISO:Extended}"),
+      # "updated" => Timex.format!(comment.updated_at, "{ISO:Extended}")
+    }
+    |> Map.merge(Utils.make_json_ld_header())
+  end
 end
diff --git a/mix.lock b/mix.lock
index 79d42914d..5d997c15c 100644
--- a/mix.lock
+++ b/mix.lock
@@ -5,7 +5,7 @@
   "absinthe_plug": {:hex, :absinthe_plug, "1.4.6", "ac5d2d3d02acf52fda0f151b294017ab06e2ed1c6c15334e06aac82c94e36e08", [:mix], [{:absinthe, "~> 1.4.11", [hex: :absinthe, repo: "hexpm", optional: false]}, {:plug, "~> 1.3.2 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
   "arc": {:hex, :arc, "0.11.0", "ac7a0cc03035317b6fef9fe94c97d7d9bd183a3e7ce1606aa0c175cfa8d1ba6d", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:ex_aws_s3, "~> 2.0", [hex: :ex_aws_s3, repo: "hexpm", optional: true]}, {:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:poison, "~> 2.2 or ~> 3.1", [hex: :poison, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm"},
   "arc_ecto": {:hex, :arc_ecto, "0.11.1", "27aedf8c236b2097eed09d96f4ae73b43eb4c042a0e2ae42d44bf644cf16115c", [:mix], [{:arc, "~> 0.11.0", [hex: :arc, repo: "hexpm", optional: false]}, {:ecto, "~> 2.1 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}], "hexpm"},
-  "argon2_elixir": {:hex, :argon2_elixir, "2.0.2", "71c0752ad38e0903140f466b465eb6f4278b16aef18ca5410d1a7cfc04a50aca", [:make, :mix], [{:comeonin, "~> 5.1", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"},
+  "argon2_elixir": {:hex, :argon2_elixir, "2.0.3", "f2272c89d6a84f85c22b9b83912fd60740bf6052bf0078e621a6e9d1127e25c8", [:make, :mix], [{:comeonin, "~> 5.1", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.5", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"},
   "atomex": {:hex, :atomex, "0.3.0", "19b5d1a2aef8706dbd307385f7d5d9f6f273869226d317492c396c7bacf26402", [:mix], [{:xml_builder, "~> 2.0.0", [hex: :xml_builder, repo: "hexpm", optional: false]}], "hexpm"},
   "bamboo": {:hex, :bamboo, "1.2.0", "8aebd24f7c606c32d0163c398004a11608ca1028182a169b2e527793bfab7561", [:mix], [{:hackney, ">= 1.13.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, ">= 1.5.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm"},
   "bamboo_smtp": {:hex, :bamboo_smtp, "1.6.0", "0a3607b77f22554af58c547350c1c73ebba6f4fb2c4bd0b11713ab5b4081588f", [:mix], [{:bamboo, "~> 1.0", [hex: :bamboo, repo: "hexpm", optional: false]}, {:gen_smtp, "~> 0.12.0", [hex: :gen_smtp, repo: "hexpm", optional: false]}], "hexpm"},
diff --git a/test/mobilizon_web/controllers/activity_pub_controller_test.exs b/test/mobilizon_web/controllers/activity_pub_controller_test.exs
index b5217964c..3b0eef813 100644
--- a/test/mobilizon_web/controllers/activity_pub_controller_test.exs
+++ b/test/mobilizon_web/controllers/activity_pub_controller_test.exs
@@ -7,6 +7,7 @@ defmodule MobilizonWeb.ActivityPubControllerTest do
   use MobilizonWeb.ConnCase
   import Mobilizon.Factory
   alias MobilizonWeb.ActivityPub.{ActorView, ObjectView}
+  alias MobilizonWeb.PageView
   alias Mobilizon.Actors
   alias Mobilizon.Actors.Actor
   alias Mobilizon.Service.ActivityPub
@@ -43,7 +44,7 @@ defmodule MobilizonWeb.ActivityPubControllerTest do
         |> get(Routes.page_url(Endpoint, :event, event.uuid))
 
       assert json_response(conn, 200) ==
-               ObjectView.render("event.json", %{event: event |> Utils.make_event_data()})
+               PageView.render("event.activity-json", %{conn: %{assigns: %{object: event}}})
     end
 
     test "it returns 404 for non-public events", %{conn: conn} do
@@ -51,6 +52,7 @@ defmodule MobilizonWeb.ActivityPubControllerTest do
 
       conn =
         conn
+        |> put_req_header("accept", "application/activity+json")
         |> get(Routes.page_url(Endpoint, :event, event.uuid))
 
       assert json_response(conn, 404)
@@ -63,10 +65,11 @@ defmodule MobilizonWeb.ActivityPubControllerTest do
 
       conn =
         conn
+        |> put_req_header("accept", "application/activity+json")
         |> get(Routes.page_url(Endpoint, :comment, comment.uuid))
 
       assert json_response(conn, 200) ==
-               ObjectView.render("comment.json", %{comment: comment |> Utils.make_comment_data()})
+               PageView.render("comment.activity-json", %{conn: %{assigns: %{object: comment}}})
     end
 
     test "it returns 404 for non-public comments", %{conn: conn} do
diff --git a/test/mobilizon_web/controllers/page_controller_test.exs b/test/mobilizon_web/controllers/page_controller_test.exs
index a7c871bb8..477a11d25 100644
--- a/test/mobilizon_web/controllers/page_controller_test.exs
+++ b/test/mobilizon_web/controllers/page_controller_test.exs
@@ -18,7 +18,7 @@ defmodule MobilizonWeb.PageControllerTest do
   test "GET /@actor with existing actor", %{conn: conn} do
     actor = insert(:actor)
     conn = get(conn, Actor.build_url(actor.preferred_username, :page))
-    assert html_response(conn, 200)
+    assert html_response(conn, 200) =~ actor.preferred_username
   end
 
   test "GET /@actor with not existing actor", %{conn: conn} do
@@ -29,7 +29,7 @@ defmodule MobilizonWeb.PageControllerTest do
   test "GET /events/:uuid", %{conn: conn} do
     event = insert(:event)
     conn = get(conn, Routes.page_url(Endpoint, :event, event.uuid))
-    assert html_response(conn, 200)
+    assert html_response(conn, 200) =~ event.title
   end
 
   test "GET /events/:uuid with not existing event", %{conn: conn} do
@@ -46,7 +46,7 @@ defmodule MobilizonWeb.PageControllerTest do
   test "GET /comments/:uuid", %{conn: conn} do
     comment = insert(:comment)
     conn = get(conn, Routes.page_url(Endpoint, :comment, comment.uuid))
-    assert html_response(conn, 200)
+    assert html_response(conn, 200) =~ comment.text
   end
 
   test "GET /comments/:uuid with not existing comment", %{conn: conn} do