Skip to content

Create an asset entity

POST
/assets

Creates a new asset, optionally linking it to a previously uploaded byte stream via file_hash. If file_hash is set, the storage layer is re-pinned under asset:<new-asset-id> and the caller’s prior user: pin for that hash is dropped (the bytes are now owned by the asset, not the uploader).

object
title
required
string
>= 1 characters <= 500 characters
description
string
asset_type
required
integer format: int64
status
string
default: active
Allowed values: draft active archived
file_hash

Sha256 of a previously uploaded storage object. When set, the storage layer is re-pinned under asset:<new-id> and the caller’s user: pin (from the prior upload) is dropped.

string
nullable
file_extension
string
nullable
tags
Array<string>
metadata
object
key
additional properties
any
state_id

Optional workflow state. Must reference a row in workflow_state whose domain = 'asset'. When omitted, the asset is placed in the asset domain’s initial state (or NULL if the domain has none).

string format: uuid
nullable

Asset already exists with the same content hash and the operator’s upload.dedup_behavior is warn — returns the EXISTING asset (no new row created) with a duplicate_warning field set so the UI surfaces a dialog. See AssetWithDedup.duplicate_warning.

Asset payload extended with a duplicate_warning field. Returned from POST /assets with HTTP 200 when the operator’s upload.dedup_behavior is warn and a duplicate hash was detected: the EXISTING asset row is returned (no new asset created), and the field signals the UI to surface a “this file was already uploaded as X” dialog.

object
id
required
string format: uuid
title
required
string
description
string
asset_type
required
integer format: int64
owner_user_ref
integer format: int64
nullable
status
required
string
Allowed values: draft active archived
file_hash
required

Sha256 of the linked storage object

string
nullable
file_extension
required
string
nullable
file_size_bytes
integer format: int64
nullable
tags
required

Flat tag value list — backwards-compatible projection that pre-1.14.B consumers depend on. New consumers prefer tag_details below which carries per-tag source

  • confidence + provenance.
Array<string>
tag_details

Per-tag source/confidence/provenance. Same set of tags as tags, ordered the same way; this field is the typed projection the frontend AssetTagBadge renders.

Array<object>

One tag with full source/confidence/provenance metadata. Backs the per-source UI badge.

object
value
required

The tag value itself (e.g. “kittens”).

string
source
required

Where the tag came from. manual = operator typed it in the UI; ai = generated by an AI tag job; import = bulk CSV / EXIF / upstream system. AI runs never overwrite manual or import rows.

string
Allowed values: manual ai import
confidence

Provider-supplied confidence in [0, 1] when source=‘ai’; null for manual and import tags. UI filter threshold lives in ai.tag.confidence_threshold (default 0.5).

number format: double
nullable
created_by_provider

Provider slug (openai / claude / ollama) for source=‘ai’.

string
nullable
created_by_model

Model identifier (gpt-4o / claude-3-5-sonnet) for source=‘ai’.

string
nullable
metadata

Free-form JSONB metadata blob (EXIF, custom fields, etc.).

object
key
additional properties
any
state_id

Optional workflow state (domain = ‘asset’).

string format: uuid
nullable
processing_status
required

Post-upload processing pipeline state. pending means background jobs (variant generation, EXIF extraction, etc.) are still in flight — the original file at /file is always available, but /variants/{key} URLs may 404 until processing completes. ready is the steady state. failed surfaces in the UI as a retry affordance.

string
Allowed values: pending ready failed
preview_available
required

True iff a servable col thumbnail variant exists for this asset AND the caller passes the content plane (visibility.CanReadContent, ADR 0064). The client renders the thumbnail only when this is true and otherwise shows a placeholder WITHOUT requesting bytes — so a content-gated or preview-less asset never fires a /variants/col (or /file) request that would 404 and spam the console (#471). A restricted asset the caller may not read reads identically to “no preview” (false), so this never confirms restricted (0064-safe).

boolean
thumbhash
required

Base64-encoded thumbhash (~30 bytes decoded) for instant blurred placeholder rendering on feed cards. Computed synchronously at upload for image assets. NULL for non-image resources.

string
nullable
created_at
required
string format: date-time
updated_at
required
string format: date-time
deleted_at

Soft-delete timestamp. Non-null only on rows surfaced by the admin include_deleted=true listing (the trash view); null on live rows.

string format: date-time
nullable
deleted_reason

Optional reason captured at soft-delete time.

string
nullable
subtitle_tracks

WebVTT subtitle / caption tracks attached to this asset. Empty array when none, or when the asset’s renderable kind doesn’t support subtitles (image, 3D, document, etc.). Tracks ride along with the asset; they do NOT appear in asset-count queries and do NOT federate as standalone activities.

Array<object>

One WebVTT subtitle track attached to an asset. NOT a first-class asset; rides along with its parent in the Asset.subtitle_tracks array. Tracks are uniquely identified by (asset_id, lang) — re-uploading the same lang replaces the existing row rather than creating a duplicate.

object
lang
required

RFC 5646 / BCP 47 language tag (e.g. “en”, “en-US”, “ja”, “fr-CA”). The sentinel value “und” is reserved for sidecar files that matched by basename but had no inferrable language segment.

string
label

Optional human-readable label (“English (US)”, “Forced”, “Director’s commentary”). Empty string when not provided; never null.

string
file_hash
required

CAS hash of the stored WebVTT file.

string
source_format
required

The format the track was uploaded in. The conversion worker stores WebVTT regardless; tracking the source lets operators re-convert if a converter bug surfaces.

string
Allowed values: vtt srt ssa ass sub idx
confidence
required

1.0 for text-based sources (deterministic conversion). <1.0 for OCR’d bitmap sources like IDX (DVD subtitles). UI MAY surface a warning banner below 0.8.

number format: float
<= 1
created_at
string format: date-time
duplicate_warning
required
object
existing_asset_id
required

The asset id the caller is being redirected to (same as id at the top level — duplicated here so client code can branch on the presence of duplicate_warning without re-reading the top-level id).

string format: uuid
message
required

Human-readable warning surfaced in the UI dedup dialog.

string

Asset created

object
id
required
string format: uuid
title
required
string
description
string
asset_type
required
integer format: int64
owner_user_ref
integer format: int64
nullable
status
required
string
Allowed values: draft active archived
file_hash
required

Sha256 of the linked storage object

string
nullable
file_extension
required
string
nullable
file_size_bytes
integer format: int64
nullable
tags
required

Flat tag value list — backwards-compatible projection that pre-1.14.B consumers depend on. New consumers prefer tag_details below which carries per-tag source

  • confidence + provenance.
Array<string>
tag_details

Per-tag source/confidence/provenance. Same set of tags as tags, ordered the same way; this field is the typed projection the frontend AssetTagBadge renders.

Array<object>

One tag with full source/confidence/provenance metadata. Backs the per-source UI badge.

object
value
required

The tag value itself (e.g. “kittens”).

string
source
required

Where the tag came from. manual = operator typed it in the UI; ai = generated by an AI tag job; import = bulk CSV / EXIF / upstream system. AI runs never overwrite manual or import rows.

string
Allowed values: manual ai import
confidence

Provider-supplied confidence in [0, 1] when source=‘ai’; null for manual and import tags. UI filter threshold lives in ai.tag.confidence_threshold (default 0.5).

number format: double
nullable
created_by_provider

Provider slug (openai / claude / ollama) for source=‘ai’.

string
nullable
created_by_model

Model identifier (gpt-4o / claude-3-5-sonnet) for source=‘ai’.

string
nullable
metadata

Free-form JSONB metadata blob (EXIF, custom fields, etc.).

object
key
additional properties
any
state_id

Optional workflow state (domain = ‘asset’).

string format: uuid
nullable
processing_status
required

Post-upload processing pipeline state. pending means background jobs (variant generation, EXIF extraction, etc.) are still in flight — the original file at /file is always available, but /variants/{key} URLs may 404 until processing completes. ready is the steady state. failed surfaces in the UI as a retry affordance.

string
Allowed values: pending ready failed
preview_available
required

True iff a servable col thumbnail variant exists for this asset AND the caller passes the content plane (visibility.CanReadContent, ADR 0064). The client renders the thumbnail only when this is true and otherwise shows a placeholder WITHOUT requesting bytes — so a content-gated or preview-less asset never fires a /variants/col (or /file) request that would 404 and spam the console (#471). A restricted asset the caller may not read reads identically to “no preview” (false), so this never confirms restricted (0064-safe).

boolean
thumbhash
required

Base64-encoded thumbhash (~30 bytes decoded) for instant blurred placeholder rendering on feed cards. Computed synchronously at upload for image assets. NULL for non-image resources.

string
nullable
created_at
required
string format: date-time
updated_at
required
string format: date-time
deleted_at

Soft-delete timestamp. Non-null only on rows surfaced by the admin include_deleted=true listing (the trash view); null on live rows.

string format: date-time
nullable
deleted_reason

Optional reason captured at soft-delete time.

string
nullable
subtitle_tracks

WebVTT subtitle / caption tracks attached to this asset. Empty array when none, or when the asset’s renderable kind doesn’t support subtitles (image, 3D, document, etc.). Tracks ride along with the asset; they do NOT appear in asset-count queries and do NOT federate as standalone activities.

Array<object>

One WebVTT subtitle track attached to an asset. NOT a first-class asset; rides along with its parent in the Asset.subtitle_tracks array. Tracks are uniquely identified by (asset_id, lang) — re-uploading the same lang replaces the existing row rather than creating a duplicate.

object
lang
required

RFC 5646 / BCP 47 language tag (e.g. “en”, “en-US”, “ja”, “fr-CA”). The sentinel value “und” is reserved for sidecar files that matched by basename but had no inferrable language segment.

string
label

Optional human-readable label (“English (US)”, “Forced”, “Director’s commentary”). Empty string when not provided; never null.

string
file_hash
required

CAS hash of the stored WebVTT file.

string
source_format
required

The format the track was uploaded in. The conversion worker stores WebVTT regardless; tracking the source lets operators re-convert if a converter bug surfaces.

string
Allowed values: vtt srt ssa ass sub idx
confidence
required

1.0 for text-based sources (deterministic conversion). <1.0 for OCR’d bitmap sources like IDX (DVD subtitles). UI MAY surface a warning banner below 0.8.

number format: float
<= 1
created_at
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"
}

Asset with the same content hash already exists in the user’s library and the operator’s upload.dedup_behavior is block. Response carries the existing asset id under existing_asset_id so the UI can navigate there.

409 response shape returned from POST /assets when upload.dedup_behavior is block and a duplicate hash was detected. The caller is expected to either navigate to the existing asset or change their upload.

object
error
required

Human-readable error message.

string
existing_asset_id
required

The asset id the duplicate hash already maps to.

string format: uuid

Unexpected server error

object
error
required

Human-readable error summary

string
Example
the request could not be completed