Skip to content

Operator-curated teams for the teams rail (GitHub

GET
/featured/teams

The featured_items placements with subject_kind = 'team' at scope = 'org', in curation order — the slot that runs first in the teams rail.

org rather than public is deliberate and it is what the write endpoint already produces: POST /admin/featured writes scope = 'org', and ADR 0065 defines org as the internal, signed-in audience — which is exactly this rail’s audience, since it renders only for a signed-in reader holding teams.read. A public placement is a separate audience with a separate surface (GET /featured) and is not read here.

A placement is not a grant. Featuring a team makes nothing visible that was not already: the join drops any team that is not live, the endpoint holds the same teams.read gate as the rest of the teams rail, and each team’s picture goes through the render-time hero re-check (#982) rather than the stored pointer, so a hero whose asset stopped being public falls back to initials here exactly as it does everywhere else.

Sits under /featured rather than /teams/featured so the path cannot be confused with /teams/{id} by a router or a reader.

Featured teams the caller may see, in curation order.

Array<object>
object
id
required
string format: uuid
slug
required

URL-friendly identifier, unique per origin server. Used in display and (eventually) federation actor URIs.

string
<= 80 characters
Example
aurora-rnd
name
required
string
<= 200 characters
Example
Aurora R&D
description
required
string
<= 500 characters
origin_server_id

Set on rows mirroring a federated team. NULL = locally owned.

string format: uuid
nullable
hero_asset_id

The team’s picture (#982) — fetch it from /assets/{hero_asset_id}/variants/col. NULL means the client falls back to the team’s initials tile.

THIS IS THE RENDER ANSWER, NOT THE STORED SETTING. The server re-derives it on every read, and it is absent whenever the chosen asset no longer qualifies: set to restricted, moved to another team, soft-deleted, or still missing its col rendition. The stored pointer survives all of those, so restoring the asset brings the picture back without an admin re-picking it — but until then the wire says “no hero” and the client paints initials.

One field rather than the setting/render pair Collection carries, because the two cannot disagree PER READER here. ADR 0088 gates a representative image per viewer; #982 narrows that for a team, because the followed-teams rail is a navigation strip and a strip that shows some teams’ pictures and not others depending on who is looking is noise rather than security. A team hero is admissible only if it is public AND owned by that team — visible to everyone or to no one — so a single field cannot be misread.

It does NOT federate: a local asset id names something that exists only on this server.

string format: uuid
nullable
parents

Direct parent teams (single hop). Empty for root teams. Closure-walked ancestors live behind the listTeams ancestor filter; this field is just the edges out of the current node.

Array<object>
object
parent_id
required
string format: uuid
parent_slug
string
parent_name
string
member_count

Direct memberships. Present on listTeams — the directory card shows it (#684) — and ABSENT from getTeam.

The asymmetry is deliberate and is about staleness, not taste. getTeam reads through an LRU that only the team-row and parent-edge endpoints invalidate; adding a membership does not, and never needed to, because nothing membership-shaped was cached. A count served from that entry would go stale the first time somebody joined. The list path is uncached, so it can carry the number honestly.

A team page that wants the count reads GET /teams/{id}/members, which it already fetches for the member strip.

integer format: int64
content_count

Live (non-soft-deleted) assets carrying this team_id — the directory card’s “how much is in here” hint (#684). Present on listTeams only, for the same caching reason as member_count.

NOT visibility-filtered, deliberately. It counts assets whose fields the caller may not read. That discloses nothing new: this operation needs teams.read, so the caller is signed in, and the authenticated asset predicate already hands them restricted rows as placeholders — the same number is reachable by paging /assets?team_id=X. Filtering it would make the card disagree with the page it links to.

integer format: int64
created_at
required
string format: date-time
updated_at
required
string format: date-time

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"
}

Authenticated but missing required capabilities

object
error
required

Human-readable error summary

string
Example
the request could not be completed