Skip to content

Choose or clear the team's hero picture

PUT
/teams/{id}/hero

Points the team at an ordinary asset to use as its picture (#982), or clears the pointer so the team falls back to its initials tile.

Why this is not part of updateTeam

The gate is different. Renaming a team is a global teams.admin act; choosing its picture is one a team’s OWN admin should be able to do, so this endpoint accepts teams.admin scoped to this team. Folding it into the PATCH would mean one operation answering to two different authorisation rules depending on which field the body happened to carry, which is how a gate ends up being widened by accident.

What the server will accept

The asset must be sensitivity: public AND carry this team’s team_id. Both halves are required: public alone would let a team pin any public asset in the install onto itself, and ownership alone would paint a team- or restricted- sensitivity picture into a strip anonymous readers can see.

An asset that fails either half is refused with the same 400 given for an id that does not exist — telling the two apart would make this an existence oracle for assets the caller cannot see.

A just-uploaded asset is accepted before its col rendition exists; the team simply keeps painting initials until the rendition lands. Refusing it would be an error the admin cannot act on.

Passing the check is not a promise the picture keeps rendering. The read path re-checks the same rule on every read, so an asset later set to restricted drops out of the payload and the team reverts to initials.

id
required
string format: uuid

Choose or clear a team’s hero picture (#982). Exactly one of the two properties must be present — an empty body is a 400 rather than a no-op, because a request that says nothing about a single-valued endpoint is a client bug, not an instruction.

object
asset_id

The asset to use as this team’s picture. Must be sensitivity: public AND carry this team’s team_id; any other asset — including one that does not exist — is refused with the same 400.

Not nullable: sending null to mean “remove” is exactly the shape that stops working the moment the Go field is a pointer with omitempty, because absent and null collapse before the handler sees them. Removal is clear_hero.

string format: uuid
clear_hero

Remove the chosen picture, reverting the team to its initials tile. Mutually exclusive with asset_id — sending both is a 400, rather than the server picking one and silently discarding the other.

Same shape, and the same reasoning, as clear_cover on CollectionUpdate and clear_expires_at before it.

boolean

The team, carrying its new hero (or none)

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

Malformed request

object
error
required

Human-readable error summary

string
Example
the request could not be completed

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

Resource not found

object
error
required

Human-readable error summary

string
Example
the request could not be completed