Skip to content
helmstudio
Contents

assets

POST /assets

Upload bytes. Idempotent on content.

R34, Q14. The body is the raw bytes. The daemon hashes them, writes `<name>.tmp`, fsyncs and renames into the blob store (06 §4), then makes the library hardlink. When the sha256 already exists, nothing is stored and the existing asset is returned with 200. Either way the caller gains read access to it (Q9). `pinned=true` pins; nothing unpins. Images are probed for width and height; video and audio take the hints given (Q13). A body over the upload limit is 413 `too_large` with `details.limit`; a body the disk cannot hold, with the same headroom rule as weight downloads, is 507 `disk_space` (`QuotaExceeded`: out of room, do not retry) before anything is written (M4 first review, #16, second review #9; the limit's value is still open). The check uses the request's Content-Length when it is sent. An asset stays out of reclaim until an item or item input first references it (M4 first review, #3).

Needs the assets capability.

GoUpload(ctx context.Context, body io.Reader, contentType string, params *AssetsUploadParams) (*Asset, error)
Pythondef upload(self, body: bytes, content_type: str, *, kind: Any, filename: Optional[Any] = None, pinned: Optional[Any] = None, width: Optional[Any] = None, height: Optional[Any] = None, duration_s: Optional[Any] = None, fps: Optional[Any] = None) -> Any
JavaScriptupload(body, contentType, params = {})
ParameterInTypeAbout
kindquery · requiredAssetKind
filenamequerystringNames the library file and the blob's extension. Defaults to the asset id plus an extension from the mime.
pinnedquerybooleanA user import. Pinned assets are never reclaimed; nothing unpins.
widthqueryinteger
heightqueryinteger
duration_squerynumber
fpsquerynumber

Request body: string (*/*)

StatusBodyMeans
200AssetThese bytes were already stored; the existing asset.
201AssetA new asset.
400ErrorThe request is malformed. Codes: `bad_request`, `bad_filter`, `bad_cursor`, `invalid_document`.
401ErrorNo studio token, or a revoked or unknown one. Code `unauthenticated`.
403ErrorThe token lacks a capability (`capability_required`, with `details.capability`), the job is not a task the studio may change (`not_a_task`), or the request came from another origin (`bad_origin`).
413ErrorThe body is over its limit. Code `too_large`, with `details.limit` in bytes.
507ErrorThe disk cannot hold the body with headroom; nothing was written. Code `disk_space`, with `details.needed` and `details.free` in bytes.

GET /assets/{id}

The asset's bytes, with Range.

R37. Readable by the rules in Q9; anything else is 404. `ETag` is the sha256. A single byte range answers 206; an unsatisfiable one 416 (`Invalid`). When the row exists and its file does not, 410 `asset_missing`. Needs the Bearer header; a studio's page loads it through the runtime SDK's same-origin proxy, which adds the header, as `/helm/api/v1/assets/{id}` (M6 Q10).

Needs the assets capability.

GoRead(ctx context.Context, id string, params *AssetsReadParams) (*RawResponse, error)
Pythondef read(self, id: str, *, range: Optional[Any] = None) -> Any
JavaScriptread(id, params = {})
ParameterInTypeAbout
idpath · requiredULID
Rangeheaderstring
StatusBodyMeans
200stringThe whole file.
206stringThe requested range.
401ErrorNo studio token, or a revoked or unknown one. Code `unauthenticated`.
403ErrorThe token lacks a capability (`capability_required`, with `details.capability`), the job is not a task the studio may change (`not_a_task`), or the request came from another origin (`bad_origin`).
404ErrorAbsent, deleted, or not the caller's to see. Code `not_found`.
410ErrorThe asset's row exists and its file does not. Code `asset_missing`.
416ErrorRange not satisfiable. Code `range_not_satisfiable`.

GET /assets/{id}/lineage

Downstream provenance — every item ever made from this asset, recursively.

02 §8, Q16. Follows `item_inputs` from the asset to the items that used it, then from each of those items' assets onward. Other studios' items are left out unless the caller holds `gallery.read_all`; the walk still passes through them, so a later own item is found. Ordered `created_at` descending. Soft-deleted items are left out of the result, and the walk passes through them too.

Needs the gallery capability.

GoLineage(ctx context.Context, id string, params *AssetsLineageParams) (*ItemPage, error)
Pythondef lineage(self, id: str, *, limit: Optional[Any] = None, cursor: Optional[Any] = None) -> Any
JavaScriptlineage(id, params = {})
ParameterInTypeAbout
idpath · requiredULID
limitqueryinteger
cursorquerystringThe `next_cursor` of the previous page, unchanged.
StatusBodyMeans
200ItemPageA page of items.
400ErrorThe request is malformed. Codes: `bad_request`, `bad_filter`, `bad_cursor`, `invalid_document`.
401ErrorNo studio token, or a revoked or unknown one. Code `unauthenticated`.
403ErrorThe token lacks a capability (`capability_required`, with `details.capability`), the job is not a task the studio may change (`not_a_task`), or the request came from another origin (`bad_origin`).
404ErrorAbsent, deleted, or not the caller's to see. Code `not_found`.

GET /assets/{id}/thumb

A thumbnail, generated once and cached.

R36, Q13. Images only in M4; video and audio answer 501 `unsupported` until the ffmpeg decision. The only width is 320 (02 §5's `thumb-320`). Needs the Bearer header; a studio's page loads it through the runtime SDK's same-origin proxy, as `/helm/api/v1/assets/{id}/thumb` (M6 Q10).

Needs the assets capability.

GoThumb(ctx context.Context, id string, params *AssetsThumbParams) (*RawResponse, error)
Pythondef thumb(self, id: str, *, w: Optional[Any] = None) -> Any
JavaScriptthumb(id, params = {})
ParameterInTypeAbout
idpath · requiredULID
wqueryinteger (320)
StatusBodyMeans
200stringThe thumbnail.
401ErrorNo studio token, or a revoked or unknown one. Code `unauthenticated`.
403ErrorThe token lacks a capability (`capability_required`, with `details.capability`), the job is not a task the studio may change (`not_a_task`), or the request came from another origin (`bad_origin`).
404ErrorAbsent, deleted, or not the caller's to see. Code `not_found`.
410ErrorThe asset's row exists and its file does not. Code `asset_missing`.
501ErrorThis provider cannot serve this. Code `unsupported`, with a reason in `message`.

POST /assets:adopt

Adopt a file from the stage or data directory by hardlink — no copy, no HTTP body.

R34, 07 §4, Q10 as amended by the M4 first review (#4), Q11. **Where the file may be.** `path` must resolve, with no symlink in it, inside the caller's stage directory (`HELM_STAGE_DIR`, `paths.stage`) or its data directory (`{data}`, `paths.data`). Anywhere else is 422 `outside_roots`. It must be a regular file (422 `not_a_file`). **What happens to it.** The file is hardlinked into the blob store and made read-only (0444). - From stage, the stage entry is then unlinked. - From data, the studio's file stays where it is. It is the blob's inode, so it is read-only too. **When the bytes already exist.** Nothing is stored and the existing asset returns with 200 (02 §7). A stage entry is unlinked; a data file is left alone. **Adoption never copies.** A blob hardlink that fails across volumes is 422 `cross_device`, and the file is left in place. A library hardlink that fails across volumes still adopts, with `library_path` null and a warning. An asset stays out of reclaim until an item or item input first references it (M4 first review, #3).

Needs the assets capability.

GoAdopt(ctx context.Context, body AdoptRequest) (*Asset, error)
Pythondef adopt(self, body: Dict[str, Any]) -> Any
JavaScriptadopt(body)

Request body: AdoptRequest (application/json)

StatusBodyMeans
200AssetThese bytes were already stored; the existing asset.
201AssetA new asset.
400ErrorThe request is malformed. Codes: `bad_request`, `bad_filter`, `bad_cursor`, `invalid_document`.
401ErrorNo studio token, or a revoked or unknown one. Code `unauthenticated`.
403ErrorThe token lacks a capability (`capability_required`, with `details.capability`), the job is not a task the studio may change (`not_a_task`), or the request came from another origin (`bad_origin`).
404ErrorAbsent, deleted, or not the caller's to see. Code `not_found`.
422ErrorWell-formed but refused. Codes: `outside_roots`, `cross_device`, `not_a_file`, `unknown_session`, `self_handoff`, `not_launchable`, `blocked`, `not_linkable`.