Skip to content

Clear a field value

DELETE
/assets/{id}/fields/{field_id}
id
required
string format: uuid
field_id
required
string format: uuid
if_unchanged_since
string format: date-time

PER-FIELD optimistic concurrency for a removal (#1119). The token is the value row’s own set_at, as AssetFieldValueWrite.if_unchanged_since describes it. The row is deleted only if it still carries exactly that set_at; otherwise the operation answers 409 with AssetFieldValueConflict and DELETES NOTHING — a stale removal must not erase a newer value it never saw.

It rides in the query string because a DELETE carries no body. There is no if_absent companion: “remove it only if it is not there” has nothing to remove.

Omitting it is the unguarded removal this endpoint has always performed, 204 whether or not a row existed, kept for callers that are not an edit surface.

Refused with 400 on a mirrored field, for the reason given on the write schema.

Cleared

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

The guarded write was refused: the stored value is not the version the caller guarded against, and nothing was stored.

A guarded field-value write was refused because the stored row is not the version the caller guarded against (#1119).

Nothing was stored. The body carries what the caller needs to reconcile without a second round trip: whether a value is there NOW, and if so what it is, including the set_at a retry would guard against.

current is REQUIRED and NULLABLE, so the key is ALWAYS present. present: false with current: null is the cleared case, and a client re-baselines it to if_absent: true; an omitted key would be indistinguishable from a server that forgot to send one. There is deliberately no fabricated set_at for a row that does not exist.

object
error
required
string
field_id
required

The field whose value was not written.

string format: uuid
present
required

Whether a value row exists for this field on this subject right now. True means somebody else’s write is there; false means the value was removed.

boolean
current
required

The value as stored now, or JSON null when present is false. Its set_at is the token a follow-up guarded write should carry.

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

The request is well-formed but the value cannot be stored.

ONE response shared by the asset and the collection field-value writers, so the two cannot drift into describing the same refusal differently. field is the field’s code and option the slug that was refused, so a client can point at the offending term without parsing error.

  • field_not_for_collection — the field is asset-scoped.
  • value_type_mismatch — the value is in the wrong member for the field’s declared type.
  • unknown_optionselect / multi_select / tree only: the slug is not in the field’s controlled vocabulary.
  • option_not_offerable — the slug names a deprecated or archived term the record did not already hold. Terms are never hard-deleted, so a value already carrying a retired slug keeps saving; only changing to one is refused.
  • dangling_referencereference only: value_ref names an asset that does not resolve. Refused on write; a value whose target is deleted after a valid write still reads (bare id).
  • vocabulary_extension_forbidden — the field is open and the term would have been created, but the caller does not hold fields.vocabulary.extend.
  • field_read_only — the field declares read_only, so a person may not set or clear its values.
  • pattern_mismatch — the value does not match the field’s regexp_filter across its whole length.
  • field_required — the field declares required, so a person may neither write an empty value into it nor remove the value it holds (#1389).

Why a well-formed field-value write could not be stored.

ONE body shared by the asset and the collection writers (both return it via the FieldValueUnprocessable response), so the two cannot drift into describing the same refusal differently. field is the field’s code and option the slug that was refused, so a client can point at the offending term without parsing error.

object
error
required
string
reason
required
  • field_not_for_collection — the field is asset-scoped and cannot be written on a collection.

  • value_type_mismatch — the value arrived in the wrong value_* member for the field’s declared type.

  • unknown_optionselect / multi_select / tree only: the slug is not a term in the field’s controlled vocabulary. For tree the whole option tree is searched, not just the top level, and a branch slug is as valid as a leaf.

  • option_not_offerable — the slug names a deprecated or archived term that the record did not already hold. Options are never hard-deleted, so a record already carrying a since-retired term keeps saving; only changing to one is refused.

  • dangling_referencereference only: value_ref names an asset that does not resolve (never existed, or has been deleted). Refused on WRITE so a value cannot be pointed at a nonexistent asset. Note the asymmetry with READ: a value that resolved when written and whose target is deleted later still reads fine, degrading to the bare id (resolved_reference simply absent) — the write gate does not retroactively invalidate it.

  • vocabulary_extension_forbidden — the field IS open, and the term would have been created, but this caller does not hold fields.vocabulary.extend. Distinct from unknown_option on purpose: the term is absent in both cases, but here the fix is a grant rather than a correction, and a client that cannot tell them apart cannot say which. Reported per TERM rather than as a bare 403 because a write may carry five terms of which four already exist — naming the one that was refused is strictly more useful than refusing the principal.

  • field_read_only — the field declares read_only, so a person may not set or clear its values (#1173). NOT a 403: no capability grants this and no grant would lift it, so reporting it as a permission problem would send the operator hunting for a role that does not exist. The field’s configuration is the thing to change.

  • pattern_mismatch — the field declares a regexp_filter and the supplied value does not match it across its whole length. Applies to human writes only; defaults and extraction are not checked, so a stored value may legitimately fail the pattern a new one is held to.

  • field_required — the field declares required, so a person may neither write an EMPTY value into it nor remove the value it holds (#1389). What “empty” means is per type: NULL or whitespace-only text; a multi_select with no members; a NULL value_num, value_date or value_ref; and for rich_text, markup carrying no visible words, since the sanitiser strips no empty elements and <p><br></p> would otherwise pass. FALSE is a real boolean value and is never empty.

    The rule is the LATER-write rule and applies to the ordinary asset and collection field-value paths. It is not a completeness gate: asset creation still requires nothing, and collection create keeps its own separate required_collection_field_missing refusal. Human writes only — upload defaults, extraction and the mirrored-column filler are unaffected, exactly as with field_read_only.

string
Allowed values: field_not_for_collection value_type_mismatch unknown_option option_not_offerable dangling_reference vocabulary_extension_forbidden field_read_only pattern_mismatch field_required
field

The code of the field that refused the value.

string
nullable
option

The offending slug, for the two vocabulary reasons. Null otherwise.

string
nullable