Assets
The images you store. Three ways in — one call with the bytes as the body, the three-step upload for large files and batches, or a public URL this service fetches — then search, collections and tags to find them again, publishing for a public URL, and delete. A job never rewrites an asset: what it makes is a new one.
14 endpoints under /api/v1/assets. What every call shares is on the REST overview, and what they are for — with the calls for every SDK, the CLI and MCP — on Assets. * marks a required field.
List and search assets
GET /api/v1/assets
Paged — page and perPage, or cursor, in; meta out
Paginated list of the caller's assets, newest-created first. Every filter is optional and they compose: ?tag=cat&mime=image/jpeg&minWidth=2000.
Each row is an AssetSummary — what an asset is called, what it is, how it is labelled and published, and when it expires. The image facts, metadata and lineage are only on GET /api/v1/assets/{id}. To walk a whole library, follow meta.nextCursor as cursor rather than counting pages: a cursor page counts nothing. A chain job's intermediate products are left out unless includeIntermediate=true.
USE THIS WHEN:
- Listing a view (all, published) or one collection
- Filtering by tag, MIME type, pixel dimensions, capture date or source operation
- Free-text search over the name and camera make/model
DO NOT USE WHEN:
- Retrieving a single asset by ID → use GET /api/v1/assets/{id}
- Listing the collections that exist → use GET /api/v1/assets/collections
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| view | query | string | Which slice of the library to read: ALL, or PUBLISHED for only the assets that carry a publicUrlone of ALL · PUBLISHED |
| collection | query | string | Collection (an opaque label you set on upload, on a job or with POST /api/v1/assets/update), matched exactly; omit for every collection |
| tag | query | string | One of your tags, matched exactly (case included) against the tags you set on upload or with POST /api/v1/assets/update |
| hasCollection | query | boolean | Whether the asset is in a collection at all: false is everything you have not filed, which no other filter can express. Cannot be combined with collection, which already says the asset is in one |
| mime | query | string | MIME type as stored (image.mimeType), matched exactly, e.g. image/jpeg |
| jobId | query | string | The job that produced these assets — everything one run left behind. An upload has no job and never matches |
| op | query | string | The op that produced these assets (their lineage.op), as GET /api/v1/ops names it. Recorded when the job was submitted with an op, and on each segment of a chain: an upload never matches, and neither does the output of a job submitted by type or by a one-segment preset |
| source | query | string | Where it came from: an upload, or the kind of job that made itone of upload · process · ai-generate · ai-edit · render |
| minWidth | query | integer (int32) | Pixel width lower bound (inclusive) |
| maxWidth | query | integer (int32) | Pixel width upper bound (inclusive) |
| minHeight | query | integer (int32) | Pixel height lower bound (inclusive) |
| maxHeight | query | integer (int32) | Pixel height upper bound (inclusive) |
| takenFrom | query | string | Capture date lower bound (EXIF DateTimeOriginal, the asset's takenAt). Epoch millis, or an ISO-8601 date or instant read as UTC: 2026-09-14, 2026-09-14T08:30:00Z. An image that records no capture date never matches a capture bound |
| takenTo | query | string | Capture date upper bound. Same forms as takenFrom; a bare date includes the whole of that day |
| createdFrom | query | string | Created-at lower bound — when this service made or ingested the asset, which is the axis an automation's output lies along. Epoch millis, or an ISO-8601 date or instant read as UTC: 2026-09-14, 2026-09-14T08:30:00Z |
| createdTo | query | string | Created-at upper bound. Same forms as createdFrom; a bare date includes the whole of that day |
| status | query | string | Ingest state: PROCESSING while the worker is still reading the bytes, DONE once it can be processed or published, FAILED for an upload whose ingest never finished (a tombstone — it expires a day after it was made and does not count against your plan)one of PROCESSING · DONE · FAILED |
| q | query | string | Free text: a substring of the name or of the camera make and model, case-insensitive |
| includeIntermediate | query | boolean | Include a chain job's intermediate products — the asset one segment made for the next to read (imagestep#246). They are out of the library by default: they exist for as long as the run does, and are deleted when their item settles. |
Returns 200 — data is AssetSummary[]
One page of rows; meta carries hasMore and nextCursor
Errors
| Status | Code and when |
|---|---|
| 400 |
|
List your collections
GET /api/v1/assets/collections
Paged — page and perPage, or cursor, in; meta out
Every collection your assets are in, one row each: the name, how many assets are in it and when the latest one was created — most recently added to first. count is the total GET /api/v1/assets?collection= reports for that name. q narrows the list to names containing it, case-insensitively. A chain job's intermediates are not counted and their job: collections are not listed.
USE THIS WHEN:
- Finding out which collections exist before filtering or submitting into one
- Checking a collection name's spelling (a misspelt name is simply a new collection)
DO NOT USE WHEN:
- Listing the assets in a collection → use GET /api/v1/assets?collection=
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| q | query | string | Only names containing this text (case-insensitive) |
Returns 200 — data is CollectionSummary[]
One page of collections; meta carries hasMore and nextCursor
Errors
| Status | Code and when |
|---|---|
| 400 |
|
Rename a collection
POST /api/v1/assets/collections/rename
Accepts an Idempotency-Key
Moves every one of your assets in from to to, in one statement — the way to rename a collection, or with to: "" to take its assets out of any collection. Renaming onto a name that is already in use merges the two. updated is how many assets moved; a name nothing is in moves nothing and is not an error. Both names are checked like any other collection name, and a chain job's job: collections cannot be renamed.
USE THIS WHEN:
- Renaming, merging or dissolving a collection
DO NOT USE WHEN:
- Moving some assets, not all of a collection → use POST /api/v1/assets/update with
collection
Request body — RenameCollectionRequest
Returns 200 — data is CollectionRenamed
What moved
Errors
| Status | Code and when |
|---|---|
| 400 |
|
Batch delete assets permanently
POST /api/v1/assets/delete
Accepts an Idempotency-Key
Deletes multiple assets and queues their stored objects for removal. Final — there is no trash and no restore. IDs that are not yours, or no longer exist, are skipped without an error: deleted lists what was actually deleted. A published asset's publicUrl stops answering within about two minutes.
USE THIS WHEN:
- Deleting specific assets by IDs
DO NOT USE WHEN:
- Letting assets lapse on their own → do nothing; each is deleted at its expiresAt
Request body — AssetBatchDeleteRequest
Returns 200 — data is BatchDeleteResultDTO
What was deleted
Errors
| Status | Code and when |
|---|---|
| 400 |
|
Finalize file uploads
POST /api/v1/assets/finish-upload
Accepts an Idempotency-Key
Creates one asset per staged object whose bytes were PUT to the staged URL.
Send only what the caller knows: which staged object, the file's name and, optionally, a collection and tags — [{"objectId": "…", "name": "photo.jpg", "collection": "shoot-01"}]. The type comes from the name's extension (the rule stage-upload signed the PUT with); size, dimensions and SHA-1 are measured by ingest; the source is always upload. Every asset starts PROCESSING — poll POST /api/v1/assets/status until it is DONE.
Every item is checked before any is written, so one bad item fails the whole request and creates nothing. At most 500 objects per request. The asset limit is checked here, and this is the check that counts — stage-upload's is advisory.
USE THIS WHEN:
- Finalizing file uploads after uploading to staged URLs (single or batch)
DO NOT USE WHEN:
- Staging the upload → use POST /api/v1/assets/stage-upload
- The image is at a public URL → use POST /api/v1/assets/from-url
Request body — FinishUploadRequest[]
Returns 200 — data is FinishedUpload[]
One new asset per object, in request order
Errors
| Status | Code and when |
|---|---|
| 400 |
|
| 422 |
|
Ingest images from URLs
POST /api/v1/assets/from-url
Accepts an Idempotency-Key
Fetches each public image URL here, stores it and creates an asset — the same asset an upload creates, in PROCESSING until ingest has written dimensions and metadata.
One result per URL, in request order: {url, id, status, name} or {url, error} with the usual code / retryable / param. A private or unresolvable address, a URL that is not an image (unsupported_format), a body over the limit or a host that fails for now (provider_unavailable, retryable) fails that URL only. As on POST /api/v1/assets/upload, the same bytes are the same asset: bytes the account already holds as a DONE asset (same SHA-1) come back as that asset with existing: true, and so does a URL whose bytes an earlier URL of the same request already brought in — nothing new is stored, and a duplicate keeps its own name, collection and tags. So a URL that landed can be sent again; an Idempotency-Key still replays the whole answer, the rows that failed included.
LIMITS: at most 20 URLs per call; each body at most 25 MB; redirects are not followed; the asset count for all URLs is checked before anything is fetched. A response that declares a type that is not an image (and a URL with no image extension), or a length over the limit, is refused before its body is read. The call shares the synchronous endpoints' concurrency: over your account's → 429 rate_limited, the node full → 503 provider_unavailable; both retryable, with Retry-After.
USE THIS WHEN:
- The image is somewhere on the internet and you want an
asset_idfor a job
DO NOT USE WHEN:
- You hold the bytes →
POST /api/v1/assets/upload(one file, up to 25 MB), orPOST /api/v1/assets/stage-upload→ PUT → finish-upload for larger files and batches - You only want one image transformed and back →
POST /api/v1/images/transformwith{"url": …}
Request body — FromUrlRequest
Returns 200 — data is FromUrlResult[]
One result per URL, in request order — a refused URL is a result with error, not a failed request; bytes you already hold are that asset, existing: true
Errors
| Status | Code and when |
|---|---|
| 400 |
|
| 422 |
|
| 503 |
|
Sign preview URLs for a page of assets
POST /api/v1/assets/preview-urls
Signs URLs for the preview renditions (400 px wide WebP) of up to 100 of your assets in one call, each valid for 15 minutes (ttlSeconds), and reports each asset's ingest status alongside. IDs that are not yours are left out rather than reported. A read that is a POST only because a page of ids does not fit a query string, so it takes no Idempotency-Key — a replay would hand back stale signatures.
USE THIS WHEN:
- Painting thumbnails for a listing page (one call per page, not one per image)
DO NOT USE WHEN:
- Waiting for assets to finish ingest → POST /api/v1/assets/status (nothing signed, no audit row)
- You want the full-size bytes → GET /api/v1/assets/{id}/content
- You want a stable public URL → publish the asset and use
publicUrl
Request body — PreviewUrlsRequest
Returns 200 — data is PreviewUrls
Errors
| Status | Code and when |
|---|---|
| 400 |
|
Stage file upload
POST /api/v1/assets/stage-upload
Accepts an Idempotency-Key
Step one of the three-step upload (stage → PUT the bytes to each returned url → finish-upload). The bytes go straight to the object store, never through this API, which is what lets it take up to 500 files of up to 100 MB in one call.
Each url is signed for one PUT, valid for 60 minutes, with the contentType it answers (read from fileName's extension) and a Content-Length equal to the fileSize you declared — the object store refuses any other. A file this service does not take (no image extension it accepts, or over 100 MB) comes back with error set and no url; the rest of the batch is staged. A sha1Hash matching a DONE asset you already hold sets exists and existingAssetId: reuse that asset, or upload anyway. A staged object that is never finished is deleted after a day.
The asset limit is checked here against the files it would stage, and again by finish-upload, which is the check that counts.
USE THIS WHEN:
- You have many files, or one over 25 MB (call this first, then PUT to the returned URL, then call finish-upload)
DO NOT USE WHEN:
- You hold one image of up to 25 MB → POST /api/v1/assets/upload does it in one call
- The image is at a public URL → POST /api/v1/assets/from-url
- Finalizing an upload → use POST /api/v1/assets/finish-upload
Request body — StageUploadRequest[]
Returns 200 — data is StageUploadResponse[]
One entry per file, in request order: a signed url, or an error
Errors
| Status | Code and when |
|---|---|
| 400 |
|
| 422 |
|
Poll the ingest status of up to 100 assets
POST /api/v1/assets/status
Where a batch of your assets is in ingest, in one call — the poll after an upload or a URL ingest, instead of GET /api/v1/assets/{id} per asset per tick. PROCESSING is still running; DONE and FAILED are final. width and height appear once ingest has measured them.
Nothing is signed and nothing is written to the audit log: the answer is your own assets' progress, and IDs that are not yours are absent rather than reported. A POST only because a batch of ids does not fit a query string, so it takes no Idempotency-Key — a replay would answer a stale status.
USE THIS WHEN:
- Waiting for uploaded or URL-ingested assets to finish (poll every one or two seconds)
DO NOT USE WHEN:
- You need the whole asset → GET /api/v1/assets/{id} once it is DONE
- You want the bytes → GET /api/v1/assets/{id}/content
Request body — AssetStatusRequest
Returns 200 — data is AssetStatuses
Errors
| Status | Code and when |
|---|---|
| 400 |
|
Batch update asset properties
POST /api/v1/assets/update
Accepts an Idempotency-Key
Updates the published status, the collection and the tags of multiple assets. Each field you send is applied to every listed asset; a field you omit is left alone. collection: "" takes the assets out of their collection; tags replaces the whole list ([] clears it). IDs that are not yours are skipped without an error: the answer is the assets that were updated, as full records with the change applied.
published: true gives each asset its publicUrl, the full-size image on the CDN; published: false takes it down within about two minutes.
USE THIS WHEN:
- Publishing or unpublishing one or more assets
- Putting assets in a different collection
- Tagging assets so GET /api/v1/assets?tag= finds them again
DO NOT USE WHEN:
- Permanently deleting assets → use POST /api/v1/assets/delete
- Renaming a whole collection → use POST /api/v1/assets/collections/rename
Request body — AssetBatchUpdateRequest
Returns 200 — data is Asset[]
The updated assets, in request order
Errors
| Status | Code and when |
|---|---|
| 400 |
|
Upload one image
POST /api/v1/assets/upload
Stores the request body as an asset in one call: the bytes are the body, the parameters are the query string. Send the image as the raw body with its own Content-Type (image/jpeg, image/png, …) and a Content-Length, or as multipart/form-data with a file part. The asset is created in PROCESSING and reaches DONE once ingest has written dimensions and metadata — poll POST /api/v1/assets/status or subscribe to the webhook.
The type is the declared Content-Type when it is an image type this product takes, else the extension of name (or the multipart file name); neither → 400 unsupported_format. Bytes the account already holds as a DONE asset (same SHA-1) come back as that asset with existing: true and nothing new is stored.
LIMITS: one file per call, at most 25 MB (413 payload_too_large); a raw body needs Content-Length (400 invalid_param). Over this node's concurrent-upload capacity → 503 provider_unavailable with Retry-After, retryable. No Idempotency-Key (contract §3): the same bytes are the same asset, so re-sending is already safe.
USE THIS WHEN:
- You hold one image of up to 25 MB and want an
asset_idfor a job
DO NOT USE WHEN:
- The file is over 25 MB, or you have many →
POST /api/v1/assets/stage-upload, PUT, then finish-upload (the bytes then go straight to the object store) - The image is at a public URL →
POST /api/v1/assets/from-url - You only want one image transformed and back →
POST /api/v1/images/transform
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| name | query | string | File name. Its extension types the bytes when Content-Type does not; the asset is named after its stem. Defaults to the multipart file name, else image. |
| collection | query | string | Collection to put the new asset in (optional; at most 200 characters, and names starting with job: are reserved). Ignored when the bytes match an asset you already hold |
| tags | query | string[] | Tags for the new asset (optional; repeat the parameter or separate with commas; at most 50, each at most 100 characters). A duplicate of an asset you already hold keeps that asset's own tags. |
| retentionDays | query | integer (int32) | Keep the new asset this many days instead of your plan's retention — shorter only; more is kept for the plan's time (imagestep#591). Its expiresAt says what was stamped. |
Request body — image/* or multipart/form-data
The image: raw bytes with their own Content-Type, or a multipart form with a file part
Returns 200 — data is UploadedAsset
The asset: new (PROCESSING), or the DONE one you already hold for the same bytes (existing: true)
Errors
| Status | Code and when |
|---|---|
| 400 |
|
| 413 |
|
| 415 |
|
| 422 |
|
| 503 |
|
Get asset by ID
GET /api/v1/assets/{id}
One asset's full record: the list row's fields plus image (type, pixel size, bytes, SHA-1 and the facts ingest derived), metadata (what exiftool read), takenAt and, for anything a job made, lineage. While the asset is PROCESSING its image facts are not measured yet.
USE THIS WHEN:
- Reading one asset's full record (image facts, metadata, lineage), typically once it is DONE
- Resolving a known asset ID
DO NOT USE WHEN:
- Listing assets → use GET /api/v1/assets
- Waiting for ingest to finish → POST /api/v1/assets/status polls a batch in one call
- You want the bytes → GET /api/v1/assets/{id}/content
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| id (required) | path | string | Asset ID |
Returns 200 — data is Asset
The asset
Errors
| Status | Code and when |
|---|---|
| 404 |
|
Delete an asset file permanently
DELETE /api/v1/assets/{id}
Accepts an Idempotency-Key
Deletes an asset and queues its stored objects for removal. There is no trash and no restore: the delete is final. A retry with the same Idempotency-Key replays the answer; without one, a second delete is 404 asset_not_found. A published asset's publicUrl stops answering within about two minutes.
USE THIS WHEN:
- Permanently removing one asset file by ID
DO NOT USE WHEN:
- Deleting multiple assets at once → use POST /api/v1/assets/delete
- Letting an asset lapse on its own → do nothing; it is deleted at its expiresAt
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| id (required) | path | string | Asset ID |
Returns 204 — no body
Deleted; no body
Errors
| Status | Code and when |
|---|---|
| 404 |
|
Download asset content
GET /api/v1/assets/{id}/content
Redirects (302) to a signed URL for the asset's own bytes, valid for 5 minutes. The asset does NOT need to be published — this is the private read path. The redirect is Cache-Control: private, no-store: the URL is the credential, so follow it and do not keep it.
USE THIS WHEN:
- Fetching your own asset's bytes from a script, SDK or CLI
- Previewing an asset you do not want on a public URL
DO NOT USE WHEN:
- You want a stable, cacheable, public URL → publish the asset (POST /api/v1/assets/update with published=true) and use
publicUrl
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| id (required) | path | string | Asset ID |
| variant | query | string | Which stored form to download: readable (default; the image at full size in a type a browser shows — for HEIF, RAW, PSD and the like a rendition ingest writes, the original until it has), original (the bytes as uploaded or produced) or preview (a 400 px wide WebP)one of readable · original · preview |
Returns 302 — no body
Redirect (Location) to the signed URL for the bytes. Follow it (curl -L).
Errors
| Status | Code and when |
|---|---|
| 400 |
|
| 404 |
|
Objects
Each described once; a linked type is another object on this page.
Asset
One stored image: what it is called, what it is, how it is labelled and published, when it expires and, for a job's output, how it was made
| Field | Type | Description |
|---|---|---|
| collection | string | The collection it is in — an opaque label you chose, matched exactly; absent when it is in none |
| createdAt | integer (int64) | Epoch millis; the library is listed newest-created first |
| expiresAt | integer (int64) | Epoch millis when the asset and its objects are deleted. Stamped at creation from your plan's retention and never recomputed, so a downgrade does not shorten it; an upload that fails ingest expires a day after it was created |
| id | string | Asset ID: ast_ and 32 hex digits |
| image | AssetImage | What the asset is, as ingest measured it |
| lineage | AssetLineage | How a job made it — absent on an upload. Written with the asset and never changed |
| metadata | object | Everything the file says about itself, as exiftool read it, under exiftool's own tag names — the map POST /api/v1/images/metadata answers with. Absent until ingest has read it |
| name | string | Display name — an upload's file name without its extension. Not unique; id is what addresses the asset |
| publicUrl | string | The CDN URL of the full-size image, once published; absent otherwise. Unpublishing or deleting takes it down within about two minutes |
| published | boolean | Whether it is on the public CDN — publicUrl is there when it is. Set with POST /api/v1/assets/update |
| source | string | Where it came from: upload, or the kind of job that made itone of upload · process · ai-generate · ai-edit · render |
| status | string | PROCESSING while ingest reads the bytes; DONE once it has measured them — the asset can then be processed and published; FAILED for an upload ingest could not read, kept a day so you can see whyone of PROCESSING · DONE · FAILED |
| tags | string[] | Your own labels, [] when there are none — what GET /api/v1/assets?tag= matches. A default analyze appends the tags and objects it found after yours |
| takenAt | integer (int64) | When the picture was taken, epoch millis — metadata.DateTimeOriginal, and what takenFrom / takenTo on GET /api/v1/assets filter. Absent when the image does not say |
| updatedAt | integer (int64) | Epoch millis of the last change you made to a label — name, collection, tags, published — or the creation time until then. Ingest and analysis do not move it |
AssetBatchDeleteRequest
Batch delete request with the asset IDs to delete
| Field | Type | Description |
|---|---|---|
| ids (required) | string[] | Asset IDs to delete permanently, at most 10000 |
AssetBatchUpdateRequest
Batch update request with asset IDs and properties to update
| Field | Type | Description |
|---|---|---|
| collection | string | Collection to put the assets in; "" takes them out of theirs (at most 200 characters; names starting with job: are reserved)e.g. "shoot-01" |
| ids (required) | string[] | Asset IDs to update, at most 10000 |
| published | boolean | true publishes every listed asset (it gets a publicUrl), false takes them down; omit to leave them as they are |
| tags | string[] | Replace the assets' tags with this list ([] clears them; omit to leave them). Your own labels, matched exactly by GET /api/v1/assets?tag= (at most 50, each at most 100 characters)e.g. ["hero","spring-sale"] |
AssetImage
What an asset is, as ingest measured it: type, pixel size, bytes, SHA-1, and the facts a caller would otherwise download the image to learn. A derived fact is absent when it could not be derived
| Field | Type | Description |
|---|---|---|
| animated | boolean | Whether it has more than one frame or page |
| blurhash | string | A BlurHash placeholder to paint while the image loads. It has no alpha channel: prefer thumbhash for an image with transparency |
| colorSpace | string | The colour space the file is in, e.g. srgb or cmyk (a CMYK original goes grey on the web) |
| density | number (double) | Dots per inch, when the file records it |
| dominantColor | string | The dominant colour, #rrggbb |
| hasAlpha | boolean | Whether the file has an alpha channel (app stores refuse one) |
| hasIccProfile | boolean | Whether the file embeds an ICC colour profile |
| height | integer (int32) | Pixel height; 0 until ingest has measured it |
| mimeType | string | The type it is stored as, e.g. image/jpeg |
| opaque | boolean | Whether every pixel is fully opaque — false only when the alpha channel is actually used |
| pages | integer (int32) | Frame or page count; 1 for a still image |
| phash | string | 64-bit perceptual hash, 16 hex digits: two images a few bits apart are the same picture, even re-encoded or resized. Meaningful for photographs, not flat graphics |
| sha1Hash | string | SHA-1 of the stored bytes, hex — the same bytes, not merely the same picture (that is phash) |
| size | integer (int64) | Stored size in bytes; 0 until ingest has measured it |
| thumbhash | string | A ThumbHash placeholder — smaller than blurhash, closer to the picture, and it keeps alpha |
| width | integer (int32) | Pixel width; 0 until ingest has measured it |
AssetLineage
The run that produced an asset: the job, what it ran, and what it was made from
| Field | Type | Description |
|---|---|---|
| intermediate | boolean | true while it exists only to feed a chain's next segment: out of GET /api/v1/assets unless includeIntermediate=true, not counted against your asset limit, deleted when its item settles |
| jobId | string | The job whose item made it |
| model | string | The model that made it, for a model's output |
| op | string | The op that made it, when the job was submitted with one, or the op of the chain segment that made it |
| parameters | object | The model parameters it ran with |
| presetId | string | The preset the job ran, when it ran one |
| presetVersion | integer (int32) | That preset's version at submit time — the steps that actually ran |
| prompt | string | What that model was asked, every subject placeholder expanded |
| sourceAssetId | string | The asset it was made from — always the image you sent, never an intermediate a chain made on the way. Absent for text-to-image and a template render |
| step | Step | Which segment of a chain made it; absent for a job that ran as one segment |
| templateId | string | The render template that drew it, for a render job's output |
| templateVersion | integer (int32) | That template's version — the bytes it was drawn from |
AssetStatus
One asset's ingest state
| Field | Type | Description |
|---|---|---|
| height | integer (int32) | Pixel height, once ingest has measured it |
| id | string | Asset ID |
| status | string | PROCESSING while ingest runs; DONE and FAILED are finalone of PROCESSING · DONE · FAILED |
| width | integer (int32) | Pixel width, once ingest has measured it |
AssetStatusRequest
Asset IDs to poll, at most 100
| Field | Type | Description |
|---|---|---|
| ids (required) | string[] | Asset IDs to poll: at least one, at most 100; a repeated id is answered once |
AssetStatuses
Ingest states for a batch of assets
| Field | Type | Description |
|---|---|---|
| items | AssetStatus[] | One entry per asset of yours in the request, in request order; IDs that are not yours are simply absent |
AssetSummary
One asset as a list row. GET /api/v1/assets/{id} has the rest: image facts, metadata, lineage.
| Field | Type | Description |
|---|---|---|
| collection | string | The collection it is in, if any |
| createdAt | integer (int64) | Epoch millis; the list is ordered by it, newest first |
| expiresAt | integer (int64) | Epoch millis when the asset and its objects are deleted |
| height | integer (int32) | image.height, once ingest measured it; absent before |
| id | string | Asset ID |
| mimeType | string | image.mimeType |
| name | string | Display name — an upload's file name without its extension |
| publicUrl | string | The CDN URL once published |
| published | boolean | Whether it is on the public CDN; publicUrl is there when it is |
| size | integer (int64) | image.size: the stored bytes |
| source | string | Where it came fromone of upload · process · ai-generate · ai-edit · render |
| status | string | PROCESSING until ingest measured it, then DONE or FAILEDone of PROCESSING · DONE · FAILED |
| tags | string[] | Your tags; [] when there are none |
| width | integer (int32) | image.width, once ingest measured it; absent before |
BatchDeleteResultDTO
Result of a batch delete operation
| Field | Type | Description |
|---|---|---|
| deleted | string[] | Identifiers of deleted items (IDs or slugs depending on the resource) |
| deletedCount | integer (int32) | Number of items deletede.g. 3 |
CollectionRenamed
What a rename did
| Field | Type | Description |
|---|---|---|
| from | string | The old name |
| to | string | The new name; absent when the assets were taken out of their collection |
| updated | integer (int32) | How many assets moved; 0 when nothing was in from |
CollectionSummary
One collection: its name, how many of your assets are in it, and when the latest was added
| Field | Type | Description |
|---|---|---|
| collection | string | The name, as GET /api/v1/assets?collection= takes ite.g. "shoot-01" |
| count | integer (int64) | Assets in it — the total GET /api/v1/assets?collection= reports |
| lastCreatedAt | integer (int64) | Epoch millis the newest asset in it was created; the list is ordered by it, newest first |
FinishUploadRequest
One staged object to turn into an asset
| Field | Type | Description |
|---|---|---|
| collection | string | Optional collection to put the asset in: an opaque name, no hierarchy, at most 200 characters; names starting with job: are reservede.g. "shoot-01" |
| name (required) | string | The file's name; its extension decides the typee.g. "vacation-photo.jpg" |
| objectId (required) | string | The objectId stage-upload returned for this file |
| retentionDays | integer (int32) | Keep the asset this many days instead of your plan's retention — shorter only; more is kept for the plan's time (imagestep#591). Its expiresAt says what was stamped.e.g. 7 |
| tags | string[] | Optional tags for the new asset: your own labels, matched exactly by GET /api/v1/assets?tag= (at most 50, each at most 100 characters)e.g. ["hero","spring-sale"] |
FinishedUpload
One asset finish-upload created
| Field | Type | Description |
|---|---|---|
| id | string | The new asset's ID |
| name | string | Its name: the file name without its extension |
| status | string | PROCESSING until ingest has measured it — poll POST /api/v1/assets/statusone of PROCESSING · DONE · FAILED |
FromUrlRequest
Images to ingest by URL
| Field | Type | Description |
|---|---|---|
| collection | string | Collection to put every new asset in (optional; at most 200 characters, and names starting with job: are reserved).e.g. "shoot-01" |
| retentionDays | integer (int32) | Keep every new asset this many days instead of your plan's retention — shorter only; more is kept for the plan's time (imagestep#591). Each asset's expiresAt says what was stamped.e.g. 7 |
| tags | string[] | Tags attached to every new asset (optional): your own labels, matched exactly by GET /api/v1/assets?tag= (at most 50, each at most 100 characters)e.g. ["hero"] |
| urls (required) | string[] | Public http(s) image URLs, fetched by this service: at least one, at most 20.e.g. ["https://cdn.example.com/shots/hero.png"] |
FromUrlResult
One URL's outcome: an asset (id, status, name, existing) or an error
| Field | Type | Description |
|---|---|---|
| error | ItemError | Why this URL did not become an asset; absent when it did |
| existing | boolean | true when the bytes were already an asset — a DONE one you held, or the one an earlier URL in this request became — and that asset is returned with nothing new stored. Absent when error is set |
| id | string | The asset's ID — new, or the one you already hold for the same bytes; absent when error is set |
| name | string | The asset's name: for a new one, the URL's last path segment without its extension, or image; absent when error is set |
| status | string | PROCESSING for a new asset, until ingest has measured it; DONE for one you already held. Absent when error is set |
| url | string | The URL as you sent it |
ItemError
Why one URL did not become an asset — the same fields as an error response
| Field | Type | Description |
|---|---|---|
| code | string | A code from the closed set: invalid_param (not an http(s) URL, a private or unresolvable address, a redirect, an HTTP error other than the ones below, no body), provider_unavailable (the URL's host answered 5xx, 429 or 408, or not in time; retryable), unsupported_format (not an image), payload_too_large (over the size limit) or internal_error (storing it failed on our side; retryable) |
| message | string | One sentence for a person; branch on code |
| param | string | The offending parameter: url, or file for a body over the limit |
| retryable | boolean | Whether the same URL may succeed if sent again |
PreviewUrl
One asset's preview: a signed URL for its small rendition when it exists, plus enough state to know why it does not yet
| Field | Type | Description |
|---|---|---|
| expiresAt | integer | null (int64) | When url stops working, epoch millis; absent when url is |
| height | integer | null (int32) | Pixel height, once known |
| id | string | Asset ID |
| status | string | Ingest state; a preview exists once this is DONEone of PROCESSING · DONE · FAILED |
| url | string | null | Signed URL for the preview, a 400 px wide WebP; absent while ingest is still running or after it failed |
| width | integer | null (int32) | Pixel width, once known |
PreviewUrls
Signed preview URLs for a page of assets
| Field | Type | Description |
|---|---|---|
| items | PreviewUrl[] | One entry per asset of yours in the request, in request order; IDs that are not yours are simply absent |
| ttlSeconds | integer (int64) | How long each url is valid from the moment it was signed |
PreviewUrlsRequest
Request object for signing preview URLs
| Field | Type | Description |
|---|---|---|
| ids (required) | string[] | Asset IDs to sign: at least one, at most 100; a repeated id is answered once |
RenameCollectionRequest
Rename a collection: every asset in from moves to to
| Field | Type | Description |
|---|---|---|
| from (required) | string | The collection as it is named nowe.g. "shoot-01" |
| to (required) | string | Its new name; "" takes the assets out of any collection (at most 200 characters; names starting with job: are reserved)e.g. "shoot-2026-09" |
StageUploadRequest
Request object for staging file upload
| Field | Type | Description |
|---|---|---|
| fileName | string | Original file name; its extension must be an image type this service accepts, and decides the Content-Type the PUT is signed fore.g. "photo.cr2" |
| fileSize (required) | integer (int64) | Size of the file in bytes, at most 100 MB. Signed into the URL: the PUT must send exactly this Content-Lengthe.g. 1024000 |
| sha1Hash | string | SHA-1 of the file, hex (optional): when it matches a DONE asset you already hold, the answer says soe.g. "356a192b7913b04c54574d18c28d46e6395428ab" |
StageUploadResponse
Response object for staged upload containing pre-signed URL and duplicate detection info
| Field | Type | Description |
|---|---|---|
| contentType | string | The Content-Type this presigned URL is signed for (#48). The PUT MUST send exactly this value, and a Content-Length equal to the fileSize you declared — the signature covers both, so any other value is rejected by the object store. It is derived from your file name; you do not choose it.e.g. "image/jpeg" |
| error | string | Why this file was not staged (no image extension this service accepts, or over the 100 MB limit). When present, objectId and url are absent: skip the file |
| existingAssetId | string | Id of the caller's existing asset with the same SHA1 (present only when exists=true) |
| exists | boolean | True when the caller already has an asset with this SHA1. The objectId/url above are still a fresh, EMPTY upload slot: either PUT the bytes and finish-upload as usual, or skip both and use existingAssetId. |
| objectId | string | The staged object's key — <your account id>/<uuid>. Opaque: send it back to finish-upload unchanged. Absent when error is set |
| sha1Hash | string | The sha1Hash you sent, echoed so you can match the answer to your filee.g. "356a192b7913b04c54574d18c28d46e6395428ab" |
| url | string | Pre-signed URL to PUT the bytes to, valid for 60 minutes. Absent when error is set |
Step
Which segment of a chain produced the asset
| Field | Type | Description |
|---|---|---|
| count | integer (int32) | How many segments the chain has |
| index | integer (int32) | The segment, 0-based |
UploadedAsset
The asset one upload created, or the one the account already held for the same bytes
| Field | Type | Description |
|---|---|---|
| existing | boolean | true when the account already had a DONE asset with the same SHA-1 — that asset is returned and nothing new is stored |
| id | string | Asset id |
| name | string | Asset name (the file name without its extension) |
| status | string | PROCESSING for a new asset; DONE when existing is true |