Skip to content

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.

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
notification_channels
required
object
key
additional properties
Array<string>
email_cadence

Per-event-type email cadence. Absent key = immediate. Values: immediate | hourly | daily | weekly.

object
key
additional properties
string
default_views
required

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
home_tab

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.

string
Allowed values: "" latest following
browse_layout

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.

string
Allowed values: "" grid masonry thumbnail list feed
browse_sort

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.

string
Allowed values: "" newest oldest
feed_filters
required

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
show_restricted

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:

  1. A member the caller cannot read is OMITTED from members rather than sent as a restricted: true placeholder.
  2. 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.
  3. 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.

boolean
browse_rail
required

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
hidden_team_ids

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.

Array<string>
team_order

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.

Array<string>
hidden_tags

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.

Array<string>
tag_order

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.

Array<string>
mature_content
required

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
show

Opt in to being shown mature content. Absent or false — the default for every account — hides it.

boolean
known_event_types
required

Canonical ordered list of notification event types this build knows about. Order is the recommended rendering order in the UI.

Array<string>
known_channels
required

Canonical channel names this build supports (in_app, email, future: sms, push, webhook).

Array<string>
default_channels_by_event
required

Per-event-type system default channel list. Applied at notification-write time when the user’s notification_channels doesn’t have that key.

object
key
additional properties
Array<string>

Authentication required, missing, or invalid

object
error
required

Human-readable error summary

string
Example
the request could not be completed
Example
{
"error": "authentication required: sign in and retry with a valid session or API token"
}

Unexpected server error

object
error
required

Human-readable error summary

string
Example
the request could not be completed