Skip to content

All field values for an asset

GET
/assets/{id}/fields

Every field value set on the asset, with the mirrored columns (title, description) merged in as their declared fields.

Filtered by field_definition.read_capability. A caller without the effective capability for a field does not receive its value, and the row is dropped rather than blanked. Effective means the same thing it means on GET /assets/{id}/field-composition: a global holding, or one scoped to the asset’s team where the asset has one.

Until #1173 this endpoint applied no per-field check at all, so any authenticated caller received the values of every gated field on the asset. That is closed. A caller that needs to distinguish “withheld” from “never set” asks the field-composition read, which answers exactly that and carries no values.

id
required
string format: uuid

Map of field code -> typed value record

Array<object>

One field value as seen on an asset.

object
field_id
required
string format: uuid
field_code
required
string
field_label
string
type
required
string
Allowed values: text longtext rich_text number boolean date datetime select multi_select tree reference
value_text
string
nullable
value_num
number
nullable
value_date
string format: date-time
nullable
value_options
Array<string>
nullable
value_ref
string format: uuid
nullable
set_by
required

Where this value came from. default means an upload default put it here and nothing has improved on it since — extraction is free to overwrite it, and a human editing the field replaces it with manual. It is the one provenance a client cannot claim: AssetFieldValueWrite.set_by has no default member, because a value a caller chose to send is by definition not one nobody chose.

mirror means the field declares mirrors_column and this value IS the asset column — nothing was stored under the field. It is not a claim about who last edited it, because the column carries no such record; set_at is the asset’s updated_at and set_by_user_ref is always null. Like default it is server-only: a caller cannot send it.

string
Allowed values: manual exif iptc xmp api import computed default mirror
set_at
required
string format: date-time
set_by_user_ref
integer format: int64
nullable
resolved_options

Display data for the vocabulary slugs this value holds, keyed by slug. Present only for select / multi_select / tree, and only for slugs that resolve against the field’s options document — a slug with no entry (the vocabulary dropped it, or the value predates it) is simply absent, and callers must fall back to rendering the slug itself.

asset_field_value stores the slug and never the label (ADR 0012) precisely so relabelling a term rewrites nothing. The cost of that indirection is that a reader has to resolve; this map is where the server pays it, once, for every consumer, so no display surface has to hold the field definition just to print a value.

object
key
additional properties

One controlled-vocabulary term as a reader needs to see it. Note there is no value — the map key is the slug.

object
label
required

Display text. Equal to the slug when the term carries no explicit label, which is the case for every option written in the bare-string form (see ADR 0012’s 2026-07-30 correction) — so a caller can always render this directly.

string
status
required

The term’s lifecycle state. A reader should mark anything other than active, so a value stops looking current the moment an operator retires the term behind it.

string
Allowed values: active deprecated archived
path

Ancestor labels from the root of the field’s vocabulary down to and including this term — what a tree value needs in order to display as “Europe / United Kingdom / London” when the stored value is nothing but london.

Absent for any term that sits at the top level, which is every term in a flat select / multi_select vocabulary: a one-element path would say nothing label does not. So a caller renders path.join(...) when it is present and label when it is not.

Array<string>
nullable
resolved_reference

The asset a reference value points at, as a reader needs to see it. Present only for reference values whose value_ref resolves to an asset this caller may see.

Absent is meaningful and load-bearing: it means the target did not resolve — soft-deleted, or a dangling ref to a row that no longer exists. A client MUST fall back to rendering the bare value_ref UUID and MUST NOT treat the absence as an error. Degrading to the id keeps the panel intact and leaks nothing: the id was already on the record the caller is reading.

This mirrors resolved_options exactly, and for the same reason (#775): the row carrying the value already joins the row carrying the display text, so the server pays the resolution once for every consumer rather than letting each display surface print a raw UUID.

object
id
required

The target asset’s id. Equal to the enclosing value’s value_ref — repeated here so a client holding a resolved_reference never has to reach back out of it to build the /assets/{id} link.

string format: uuid
title
required

The target asset’s title. May be the empty string — assets.title defaults to '' and nothing requires an uploader to set one — so a client renders the id when this is blank rather than an empty link.

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

Resource not found

object
error
required

Human-readable error summary

string
Example
the request could not be completed