Run a site
Privacy
Who can see what on an installation. There are three layers, and something is seen only by somebody who gets past all of them:
- The installation: what people who are not signed in may read at all, and whether anything new may be public. Set by whoever runs the site.
- The profile: who can see a person's profile or a group. Set by its owner or administrators.
- The post: who can see one post. Set by its author.
The routes that set who may see what are in the API reference, each with the audiences it takes: making everything registered-only is POST /accounts/make-registered.
The audiences
Posts, profiles and groups all use the same words:
| Audience | Who that is |
|---|---|
public |
Anybody, signed in or not |
registered |
Anybody who is signed in |
followers |
People who follow the profile or group |
leaders |
People the profile follows. People only |
mutuals |
People who follow the profile and are followed back. People only |
admins |
The administrators of the group. Groups only |
myself |
Only the owner of the profile. People only |
Each one is measured against the profile the post is on, not against
whoever wrote it: followers are that profile's followers.
The owner of a profile and its administrators always see everything on it, and an author always sees their own post where they can still see the profile. Nobody sees anything belonging to somebody they have blocked or been blocked by. The full rules are in Permissions.
Who can see a post
The author chooses when writing it, with the audience button beside Post, and whoever may edit the post can change it afterwards with the same button.
Which audiences a post may have depends on where it is:
| Where the post is | Audiences |
|---|---|
| Your own profile | public, registered, followers, leaders, mutuals, myself |
| Somebody else's profile | public, registered, followers, leaders, mutuals |
| A group, as one of its followers | public, registered, followers |
| A group, as one of its administrators, or as a site administrator | public, registered, followers, admins |
Anything else is refused with 422 access_not_allowed, and the answer lists
the audiences that are allowed.
- No "only me" on somebody else's profile. It would be a post only its author and that person can see, which is a private message. This platform does not have those.
- No "administrators only" from a follower who is not one, for the same reason.
- A post is never seen more widely than its profile. On a followers-only
group a
publicpost is still seen only by its followers. - A post written without saying is
public, orregisteredwhere the installation allows nothing new to be public. - Replies have no audience of their own. They are seen by whoever can see the post at the top of their thread.
The API: access when the post is created (POST /notes/:owner_id/, and the
same for articles, topics and photos; for a video, when its upload begins,
POST /videos/:owner_id/uploads), and PATCH /…/:id/access to change it.
Every person, group and post in a response carries authorized.audiences, the
audiences the viewer may choose there, so a client offers only those.
Who can see a profile or a group
Set on the profile's Access settings.
| Audiences | |
|---|---|
| A person | public, registered, followers, leaders, mutuals, myself |
| A group | public, registered, followers, admins |
A new account's profile is public, or registered where the installation
allows nothing new to be public. A new group starts as admins, a draft only
its administrators see, until they open it.
Somebody who cannot see a profile cannot see anything on it, whatever each post says.
What the installation allows
Two settings, independent of each other.
Visitors: SITE_READ_ACCESS
What somebody who is not signed in may read.
| Value | A visitor gets |
|---|---|
public (the default) |
Everything that is public |
preview |
Only the start of what is public |
registered |
Nothing. A members-only site |
Signed-in people see exactly the same under all three. Nothing stored changes,
so any of them can be switched to any other: a post that says public is
public again the moment the setting is public.
registered, the members-only site. Every route that serves content
answers 401 members_only to a visitor: profiles, posts, feeds, replies,
hashtags, locations, followers and search. What stays open is what people need to
get in: signing in and up, password reset, accepting an invitation, the OAuth
endpoints and NodeInfo.
preview. A visitor still reaches what is public, and is sent only the
start of it:
| What a visitor is sent | |
|---|---|
| A post's text, and a profile's | The first 280 characters or so, as plain words, cut at a word and ending in "…". The post is marked truncated: true. A text that fits is sent as it was written |
| A photo | The first image only, at its smallest size. files_held_back says how many more the post has |
| A list | The first 10 items. pagination.more is true when there are more, and pagination.total is the number sent. Asking for a later page answers 401 members_only |
| Replies | Nothing: 401 members_only. The count on the post is still there |
| Who follows a profile, and whom it follows | Nothing: 401 members_only. The counts on the profile are still there |
| Avatars and covers | As usual |
It is enough for a link shared elsewhere to say what it leads to, while the rest stays behind signing in.
New content: MIN_CONTENT_ACCESS
The widest audience anything new may be given.
| Value | |
|---|---|
public (the default) |
Anything may be public |
registered |
Nothing new may be public |
With registered:
- A post can no longer be created public or changed to public, and neither can
a person's profile or a group. The answer is 422
access_not_allowed. - A post written without saying who can see it is for signed-in people, and so is a new account's profile.
- What is already public stays public, and may stay so when it is edited. The setting governs what is written from now on.
Using them together
SITE_READ_ACCESS |
MIN_CONTENT_ACCESS |
The site |
|---|---|---|
public |
public |
Open. The default |
public |
registered |
Old public posts stay readable by visitors; nothing new is added to them |
preview |
public |
Visitors see the start of things; people still post publicly, which is what makes a preview worth having |
registered |
public |
Members-only. "Public" then means every signed-in person, and would mean everybody again if the site were opened |
registered |
registered |
Fully private, and it would stay private if the site were opened later |
Making what is already public members-only
MIN_CONTENT_ACCESS=registered does not reach back. A super administrator can
bring the rest into line, once: Site Settings › Privacy › Make everything
public members-only.
- It changes every public person, group and post to
registered. Hashtags and locations are left: they are nobody's content. - It asks for the number of things it will change to be typed (
CONVERT 8675) and for a proof of identity. - It is refused, with 409
public_content_allowed, on an installation that still allows public content: there it would be undone by the next public post. - It records who did it and how much it changed, in
site_actions. - It cannot be undone. Nothing records what was public before.
The API: GET /accounts/public-content for the counts, and
POST /accounts/make-registered with { "confirm": "CONVERT 8675" }.
Setting them
Both are environment variables read by every service, so they are set in one place and need a restart.
With docker-compose, in .env:
SITE_READ_ACCESS=registered
MIN_CONTENT_ACCESS=registered
then make up.
On Kubernetes, in the overlay's config.env, then apply it and restart the
deployments. See Deploying.
A value that is not understood is treated as registered, the strictest, and
each service says so in its log:
policy: SITE_READ_ACCESS is "privat", which is not "public", "preview" or "registered"; treating it as "registered"
Checking them
NodeInfo says what the installation is set to, and answers on every kind of site:
curl -s https://api.example.org/nodeinfo/2.1 | grep -E 'readAccess|minContentAccess'
Then, without signing in, ask for something public. SOMEONE is any handle
and 5 any person's id:
| Request | Open | Preview | Members-only |
|---|---|---|---|
GET /people/SOMEONE |
200 | 200 | 401 |
GET /notes/5/?limit=50 |
200 | 200, at most 10 items, long ones cut and marked truncated |
401 |
GET /notes/5/?start=10 |
200 | 401 | 401 |
GET /socialgraph/5/followers/ |
200 | 401 | 401 |
GET /photos/5/download (where the post allows it) |
302 | 403 | 401 |
GET /oauth/login |
200 | 200 | 200 |
What the web app does
- The audience button offers what the server says a post may have there. A choice wider than the profile is greyed out, with the reason.
- On a members-only site, a visitor sees a notice with a Sign in button
and the home page, in place of whatever they asked for and at the same
address. The search box and the People, Groups, Hashtags and Locations menu
entries are not shown. Pages carry
robots: noindex. - On a preview site, a visitor sees a notice above each page, "Sign in to read more" under a post that was cut, and a sign-in prompt where a post's replies or a profile's list of followers would be. The app does not ask the server for those.
- Where nothing new may be public, "Public" is not offered.
- Site Settings › Privacy, for super administrators, shows the two settings as the server has them.
Where it is enforced, and how it is tested
Each rule lives in one place, with tests that run without a database.
| Rule | Where | Tests |
|---|---|---|
| Reading the two settings, and treating a bad value as the strictest | anahita-libs/policy |
policy_test.go |
| Which audiences a post may have | permissions.MediumAudiences in anahita-libs/permissions/audience.go |
audience_test.go |
| Who can see a post, a profile or a group | SetMediumAccessQuery and SetActorAccessQuery in graph-grpc-service/data/repositories/ (access_medium.go, access_actor.go, access.go) |
access_test.go |
| Refusing a visitor on a members-only site | SetViewer in anahita-libs/middleware/oauth.go |
members_only_test.go, signed_in_test.go |
| Cutting what a visitor is sent on a preview site | anahita-libs/middleware/preview.go |
preview_test.go |
| Making everything public members-only | nodes_public_access.go in graph-grpc-service, which records it in table site_actions; the endpoint in auth-service (public_content.go) |
nodes_public_access_test.go, public_content_test.go |
| What NodeInfo reports | auth-service | node_info_privacy_test.go |
| Sending nothing out on a preview or members-only site | siteFederates in activitypub-service/cmd/api/handlers/handlers.go; canWrite in atproto-service/cmd/api/handlers/settings.go |
handlers_test.go (activitypub-service), out_test.go (atproto-service) |
Three tests are guards. They read the code and fail when something new goes around a rule:
TestEveryRouteNamesItsViewerreads every service's routes and fails if one has neitherAuthorizenorSetViewerin front of it. Such a route would serve visitors on a members-only site.TestTheRoutesAPreviewLeavesOutSaySochecks that replies and the follower lists are marked as left out of a preview.TestEveryHandlerThatSetsAnAudienceChecksItchecks that every handler that creates a post, changes its audience, changes a profile's access or creates an account asks the rule first.
signed_in_test.go is the other half of both restrictions: somebody who is
signed in is never refused and never sent a preview.
The services' own reads, with nobody signed in, are not restricted: writing a
notification, extracting hashtags, creating an account. They do not come
through SetViewer.
Federation
- Only
publicposts of people who chose to take part are sent to other servers: on the fediverse, people who turned federation on; on Bluesky, people who connected their own account. Only people whose profile anybody may see take part at all. - With
MIN_CONTENT_ACCESS=registered, nothing new is public, so nothing new is sent. - With
SITE_READ_ACCESSset toprevieworregistered, the installation sends nothing to the fediverse or to Bluesky, and connecting a Bluesky account is not offered. On a preview site, sending out would give away the whole of what the preview holds back. Reading from elsewhere still works. - What came from elsewhere is copied in, so a reader's browser never asks another server for it. See Federation.
From anahitasocial/anahita-services, 2026-10-27.
Written by Claude, reviewed by Rastin Mehr · About this