Images¶
Permission required: images
Read, upload, and delete image/video assets stored in Lumiverse's shared asset system. Spindle automatically tags uploads with the current extension identifier, and the list/get APIs can filter by extension ownership, character ownership, chat ownership, and requested resolution specificity.
Usage¶
// List all assets visible to the user
const { data, total } = await spindle.images.list({ limit: 20, offset: 0 })
// List only assets created by this extension, but return small thumbnail URLs
const ownedThumbs = await spindle.images.list({
onlyOwned: true,
specificity: 'sm',
})
// Further narrow to a single character or chat
const characterImages = await spindle.images.list({
onlyOwned: true,
characterId: 'char-id',
specificity: 'lg',
})
const chatImages = await spindle.images.list({
onlyOwned: true,
chatId: 'chat-id',
})
// Get a single asset DTO
const image = await spindle.images.get('image-id', {
onlyOwned: true,
specificity: 'sm',
})
// Upload raw bytes; the current extension is recorded automatically.
const uploaded = await spindle.images.upload({
data: pngBytes,
filename: 'cover.png',
mime_type: 'image/png',
owner_character_id: 'char-id',
owner_chat_id: 'chat-id',
})
// Persist raw video bytes, including output from spindle.media.*, back into
// the shared asset store.
const storedVideo = await spindle.images.upload({
data: videoBytes,
filename: 'clip.mp4',
mime_type: 'video/mp4',
transcode_video_codec: 'h264',
sidecar_video_codecs: ['hevc'],
})
// Batch upload — one IPC + host-side concurrency pool replaces N round-trips
const results = await spindle.images.uploadMany([
{ data: bytes1, filename: 'a.png', mime_type: 'image/png' },
{ data: bytes2, filename: 'b.png', mime_type: 'image/png' },
])
// Returns Array<{ id?: string; error?: string }> — per-item failures don't throw the batch
// Upload a data URL with ownership tags
const generated = await spindle.images.uploadFromDataUrl(dataUrl, {
originalFilename: 'generated.png',
owner_character_id: 'char-id',
owner_chat_id: 'chat-id',
})
// Delete an asset
await spindle.images.delete(uploaded.id)
// Bulk delete by ID
const deletedCount = await spindle.images.deleteMany(imageIds)
Methods¶
| Method | Returns | Description |
|---|---|---|
list(options?) |
Promise<{ data: ImageDTO[], total: number }> |
List stored assets. Options: { limit?, offset?, specificity?, onlyOwned?, characterId?, chatId? }. Defaults: limit 50, max 200. |
get(imageId, options?) |
Promise<ImageDTO \| null> |
Get a single asset DTO. Options: { specificity?, onlyOwned?, characterId?, chatId? }. Returns null when the asset is missing or excluded by the ownership filters. |
upload(input) |
Promise<ImageDTO> |
Upload raw image or video bytes. The host automatically tags the stored asset with the current extension identifier. |
uploadMany(items, options?) |
Promise<Array<{ id?: string; error?: string }>> |
Batch upload multiple assets in a single IPC call. Per-item failures captured as {error} rather than throwing. Options: { userId?, concurrency? } (concurrency capped at 32, default 16). |
uploadFromDataUrl(dataUrl, options?) |
Promise<ImageDTO> |
Upload a base64 image data URL. The host automatically tags the stored asset with the current extension identifier. |
delete(imageId) |
Promise<boolean> |
Delete an asset by ID. Returns true when deleted. Emits IMAGE_DELETED. |
deleteMany(imageIds, options?) |
Promise<number> |
Bulk delete by ID. Returns the number of assets deleted (missing IDs are skipped). Does not emit per-image IMAGE_DELETED events. Options: { userId? }. |
ImageListOptionsDTO¶
| Field | Type | Description |
|---|---|---|
limit |
number |
Page size. Default 50, max 200. |
offset |
number |
Pagination offset. |
specificity |
'full' \| 'sm' \| 'lg' |
Which asset URL size should be returned in each DTO. 'full' is the original asset URL, 'sm' and 'lg' return thumbnail URLs. |
onlyOwned |
boolean |
Restrict results to assets created by the current extension. |
characterId |
string |
Restrict results to assets tagged to a specific character. |
chatId |
string |
Restrict results to assets tagged to a specific chat. |
userId |
string |
Required for operator-scoped extensions. |
ImageGetOptionsDTO¶
Same ownership and specificity fields as ImageListOptionsDTO, minus pagination.
ImageDTO¶
{
id: string
original_filename: string
mime_type: string // image/* or video/*
width: number | null
height: number | null
has_thumbnail: boolean
url: string // authenticated relative URL for this specificity
specificity: 'full' | 'sm' | 'lg' // size encoded into url
owner_extension_identifier: string | null
owner_character_id: string | null
owner_chat_id: string | null
created_at: number // unix epoch seconds
}
ImageUploadDTO¶
| Field | Type | Required | Description |
|---|---|---|---|
data |
Uint8Array |
Yes | Raw image or video bytes. |
filename |
string |
No | Original filename to store with the asset. |
mime_type |
string |
No | MIME type. Defaults to image/png when omitted and not inferable. |
owner_character_id |
string |
No | Optional character ownership tag stored with the asset. |
owner_chat_id |
string |
No | Optional chat ownership tag stored with the asset. |
strip_audio |
boolean |
No | For video uploads, strip any audio tracks from the stored output when possible. |
transcode_video_codec |
'h264' \| 'hevc' |
No | For video uploads, transcode the primary stored asset to this codec. |
sidecar_video_codecs |
Array<'h264' \| 'hevc'> |
No | Generate additional stored video variants alongside the primary asset. |
ImageUploadFromDataUrlOptionsDTO¶
| Field | Type | Description |
|---|---|---|
originalFilename |
string |
Optional filename to persist with the asset. |
owner_character_id |
string |
Optional character ownership tag stored with the asset. |
owner_chat_id |
string |
Optional chat ownership tag stored with the asset. |
userId |
string |
Required for operator-scoped extensions. |
Ownership Model¶
- Every upload made through
spindle.images.upload()orspindle.images.uploadFromDataUrl()is automatically tagged withowner_extension_identifier = <current extension id>. onlyOwned: trueapplies that extension filter at read time, which avoids scanning the full user asset set when an extension only cares about its own uploads.characterIdandchatIdare additive filters. Combine them withonlyOwned: truewhen you want "only my extension's assets for this character/chat".
Specificity And URLs¶
specificity: 'full'returns/api/v1/images/{id}.specificity: 'sm'returns/api/v1/images/{id}?size=sm.specificity: 'lg'returns/api/v1/images/{id}?size=lg.- These URLs are authenticated image endpoints. Use
ImageDTO.urldirectly instead of rebuilding the path yourself.
Notes¶
spindle.images.get()returns metadata plus a URL, not the binary asset bytes themselves.- Thumbnail generation is supported automatically.
has_thumbnailtells you whether thumbnails are available or can be lazily generated.uploadManydefers thumbnail (and width/height) generation to a background job for throughput; the underlying row populates these fields asynchronously after the call returns. - Video assets stored here can be used as
spindle.mediainputs viasource: { kind: "image", image_id }. Despite the source name, that media source kind accepts still images and video assets from this store. - Generated images persisted through
spindle.imageGen.generate()also participate in this ownership model whenowner_character_idorowner_chat_idare supplied.
Note
For user-scoped extensions, the user context is inferred automatically. For operator-scoped extensions, pass userId when working on behalf of a specific user.