Get my notification + view preferences
GET /account/preferences
Per-user application-behavior preferences: which channels (in_app, email) the user wants each notification event delivered through, plus default-view selections (home tab, browse layout, browse sort) that apply to the user’s next page load.
First-visit callers (no row yet) get a zero-value response —
empty notification_channels, empty default_views, but the
canonical known_event_types, known_channels, and per-event
default_channels_by_event are always populated so the UI can
render the toggle grid without hard-coding the catalog.
Channel resolution at notification-write time falls back to
default_channels_by_event for any event the user hasn’t
explicitly set. An explicit empty array MEANS “deliver
nothing for this event” and overrides the default.
Authorizations
Section titled “Authorizations ”Responses
Section titled “ Responses ”User preferences.
GET / PATCH response for per-user preferences. Includes the build’s known event-type + channel catalogs so the UI renders toggle grids without hard-coding the lists client-side. Per-event system defaults are surfaced so the UI can show “follows system default — in_app only” hints alongside each toggle.
object
object
Per-event-type email cadence. Absent key = immediate. Values: immediate | hourly | daily | weekly.
object
Default-view selections — the SEED for a device that has no
local choice of its own, never an override of one. The browse
store reads these once at hydration and only for the keys
localStorage does not already hold, so picking masonry on
one laptop survives a reload even when the account default
says grid.
Every value set below is a closed enum, and each member is a
state the app can actually reach: home_tab mirrors the feed
enum on GET /posts, browse_layout mirrors the browse
store’s ViewMode, and browse_sort carries only the two
orderings the client can produce. Widening one of these means
first building the thing it names (#736) — a value here that
nothing can serve is a promise the UI breaks silently.
object
Which feed segment browse lands on. Empty/unset falls back
to latest. trending and for_you were removed in
#736: neither existed in the feed enum, so choosing one
put the plain latest feed under a label promising
otherwise. They come back when a ranking model does.
Default browse-feed layout. Empty/unset falls back to the
per-device default (feed on a coarse pointer, grid
otherwise). feed is selectable here as of #706 — it was
already a mode a phone could land on but not one a user
could ask for.
Default browse ordering: newest (posted_at descending,
the built-in) or oldest. Empty/unset falls back to
newest. popular and trending were removed in #706 —
GET /posts has no ranking parameter and no ranking
model, so both were labels over the default order.
Browse-feed content filters, always present with every key
populated — unlike default_views, where “unset” and “the
default” are different states the UI renders differently, a
boolean filter has no third value, so omitting it would only
make the client guess false rather than read it.
object
Keep the restricted-member placeholders (#883) in
GET /posts instead of subtracting them.
Default false — the feed hides them (#921). #891
shipped this machinery as an opt-in on the theory that the
placeholder is the more informative answer. Measured against
the stock seed dataset, a reader with no capabilities got 82
posts of which 27 were entirely placeholders: a third of the
grid was locked doors. The principle the default now encodes
is that a placeholder belongs where the reader ASKED A
QUESTION or OPENED A CONTAINER, not where they were handed a
feed.
With the default (false), three rules, and the third is the
one that matters:
- A member the caller cannot read is OMITTED from
membersrather than sent as arestricted: trueplaceholder. - A post left with NO visible members — one that HAD members and none survived — is omitted from the page. A post with no members at all (an article, ADR 0073) is unaffected: it was never showing you anything you couldn’t see.
- A post the CALLER AUTHORED is never omitted, however restricted its members are. An author’s post can carry other people’s assets, so rule 2 alone would make your own work vanish from your own feed over a display default.
Set it true and GET /posts returns the pre-#921 payload
exactly: every placeholder present, every all-restricted post
back in the page.
Applies to GET /posts and stops there. GET /posts/{id} is
an explicit request for one post and answers with the
placeholders intact whatever this is set to, which is not an
inconsistency: rule 2 exists BECAUSE an all-restricted post
rendering as an empty card is worse than the placeholder it
replaced, and filtering members on the single-post read would
rebuild that empty card on the one surface rule 2 could not
protect. Collection contents render placeholders for the same
reason — an opened container should say what is in it.
It also keeps the ask alive. #913’s “Request access” button lives on the placeholder, so it survives wherever the placeholder does: the post page and collections. The trade the DEFAULT makes — no placeholders in the feed means no Request access in the feed — is stated in the setting’s own help text rather than left to be discovered.
Browse-rail curation, always present with every list
materialised even when empty. Same argument as
feed_filters above, for the list case: “no curation” and
“an empty curation” are the same rail, so omitting it would
only make the client re-derive [].
object
Teams the reader removed from their rail. Empty = nothing hidden.
A hide-list rather than a show-list, deliberately: an empty allow-list would have to mean “show everything”, which is the opposite of what an empty allow-list says, and the first partial write would blank the rail.
The reader’s explicit ordering, applied to the FOLLOWED group (the manage panel’s drag-reorder). Empty = the server’s order.
PARTIAL LISTS ARE LEGAL and are the normal case: the ids named here lead, in this order, and everything else keeps its previous relative position behind them. That is what lets “drag one team to the top” persist one id rather than a full snapshot that the next follow would immediately make stale.
Followed tags whose chip the reader removed from their rail (#1123). Empty = nothing hidden.
HIDING IS NOT UNFOLLOWING, and the two verbs sit next to
each other in the manage panel precisely so the difference
is visible. Unfollowing drops the tag_follows row and
changes what the Following feed CONTAINS. Hiding only takes
the chip off the strip; the tag’s posts keep reaching the
feed exactly as before.
The reader’s explicit ordering of their followed-tag chips. Empty = the server’s order (most recently followed first).
Partial lists are legal and normal, exactly as team_order.
This account’s mature-content consent, always present with
show populated. Same argument as feed_filters: a boolean
has no third state, so omitting it would only make the
client guess.
object
Opt in to being shown mature content. Absent or false —
the default for every account — hides it.
Canonical ordered list of notification event types this build knows about. Order is the recommended rendering order in the UI.
Canonical channel names this build supports
(in_app, email, future: sms, push, webhook).
Per-event-type system default channel list. Applied at
notification-write time when the user’s
notification_channels doesn’t have that key.
object
Authentication required, missing, or invalid
object
Human-readable error summary
Example
the request could not be completedExample
{ "error": "authentication required: sign in and retry with a valid session or API token"}Unexpected server error
object
Human-readable error summary
Example
the request could not be completed