Repository navigation
Cloud Drive
Organise your library from code: folders (collections) and sidebar groups, moving creations, likes, bulk actions, trash/restore, permanent delete, and publishing to the public feed.
- Reading (list/search folders and creations, the feed,
random) needs thereadscope. - Organising (folders, groups, move, like, reorder, batch, trash, restore,
delete) needs
manage. - Publishing / unpublishing needs
publish.
// List — newest first, one page at a time (limit default 20, max 100)
let page = await client.folders.list({ limit: 50 });
const all = [...page.folders];
while (page.nextBeforeDate) {
page = await client.folders.list({ limit: 50, beforeDate: page.nextBeforeDate });
all.push(...page.folders);
}
// Create — optionally straight into one of your sidebar groups
const folder = await client.folders.create({ title: 'Brand assets', description: 'logos + refs' });
// Rename / re-describe
await client.folders.update(folder.folderId, { title: 'Brand kit' });
// Pin a poster, star it in the sidebar, move it between groups
await client.folders.update(folder.folderId, {
posterCreationId: gen.creationId, // null clears the poster
isFavorite: true, // feeds the smart 'favorites' lens
groupId: group.groupId, // null detaches from its group
});
// Delete — contained creations detach to the drive root…
await client.folders.delete(folder.folderId);
// …or send them to trash instead:
await client.folders.delete(folder.folderId, { trashContents: true });An account holds up to 500 folders — creating past the cap answers
409 FOLDER_LIMIT_REACHED.
list() hands back a page — { folders, nextBeforeDate } — not a bare array, so
an account with hundreds of collections never pays for all of them at once. The
cursor works exactly like the creation list's: nextBeforeDate comes back only
while a further page may exist. Calling list(signal) with a bare AbortSignal
still works.
list also takes a sort — index (the sidebar's drag-reorder order),
date-desc, date-asc, name-asc, name-desc, update-desc — the same
vocabulary the studio uses. Only the default and date-desc orderings emit
nextBeforeDate (the cursor filters on the creation date, so it can't page any
other order without skipping rows); for the rest, raise limit — at 100 it
covers any realistic drive.
const byName = await client.folders.list({ sort: 'name-asc', limit: 100 });Groups are the collapsible sections the studio's cloud-drive sidebar organises collections into. They hold folders, never creations directly:
const groups = await client.folders.groups.list(); // sidebar order
const group = await client.folders.groups.create('Client work');
await client.folders.groups.rename(group.groupId, 'Clients');
await client.folders.groups.remove(group.groupId); // detaches member folders — nothing else is deletedMove a folder in or out with folders.update(id, { groupId }) — a group id to
set, null to detach — or create it grouped from the start with
folders.create({ title, groupId }). An account holds up to 100 groups
(409 GROUP_LIMIT_REACHED), and a Folder row carries its groupId (absent
when ungrouped) plus isFavorite.
// Into a folder
await client.creations.move(gen, folder.folderId);
// Back to the drive root
await client.creations.move(gen, 'root'); // or: move(gen, null)Every method accepts a creation id or any object that carries one
(a Creation, a generation result, a QueueState).
creations.list speaks the same lenses the studio's cloud drive uses. Pick
one mode per call — the default flat listing (folderId / trashed), an
activity lens, a smart collection, a sharedFolderId, or a groupId — then
compose any of the cross-cutting filters on top. Mixing two modes rejects with 400
CONFLICTING_FILTERS.
// A folder's contents
const inFolder = await client.creations.list({ folderId: folder.folderId });
// Ungrouped root
const root = await client.creations.list({ folderId: 'root' });
// The trash
const trash = await client.creations.list({ trashed: true });
// Activity lenses — the studio's fixed collections:
// all | history | uploaded | likes | public | trash | sdk | mcp | portfolio
const uploads = await client.creations.list({ activity: 'uploaded' });
// Smart collections: faces (portraits), videos, crop (cropped images),
// alpha (transparent PNGs from erase edits), favorites (creations inside
// starred folders)
const faces = await client.creations.list({ smart: 'faces' });
// A folder someone shared WITH you (view access)
const shared = await client.creations.list({ sharedFolderId: theirFolderId });
// A folder group (the sidebar groups) — every folder filed in it, one
// listing; `creations.random({ groupId })` samples across the whole group
const grouped = await client.creations.list({ groupId: group.groupId });
// Everything BUILT FROM a given creation — any reference slot (image refs,
// face/character refs, style sources) or lineage parent (retry, crop, erase,
// clone, edit) counts, and clone families are expanded server-side, exactly
// like the studio's filter-by-reference lens.
const derived = await client.creations.list({ usedRef: gen });The cross-cutting filters compose with any lens:
const hits = await client.creations.list({
search: 'red fox*', // whole-word AND over descriptions + tags; trailing * = prefix
sort: 'size-desc', // newest|oldest|updated|size-desc|size-asc|type-asc|type-desc
ratioFilter: 'portrait', // square|landscape|portrait — or a comma set: 'square,portrait'
from: '2026-07-01', // date range — ISO or epoch ms
to: Date.now(),
});
// hideFiled: skip creations already filed into a folder — meaningful on the
// history / uploaded / likes / sdk / mcp lenses
const unfiled = await client.creations.list({ activity: 'history', hideFiled: true });search is capped at 128 characters / 8 tokens. Rows come back enriched:
description, folderId, inTrash, isPublicShared, likes, isLiked,
isOwner, nsfwFlagged, nsfwRate (own rows), and username on rows you
don't own.
// Cursor (newest first) — prefer it for deep walks
let page = await client.creations.list({ limit: 50 });
while (page.nextBeforeDate) {
page = await client.creations.list({ limit: 50, beforeDate: page.nextBeforeDate });
}
// Or 1-based offset paging — the page echoes { page, limit, count }
const p3 = await client.creations.list({ sort: 'size-desc', page: 3, limit: 50 });nextBeforeDate is only emitted when rows are in newest-first date order (the
default sort, no relevance-ranked search) — a date cursor can't walk any
other ordering without skipping or repeating rows, so everything else pages
with page instead.
creations.random returns one uniformly-sampled creation from the whole
filtered collection (or null when nothing matches). Same filters as list,
no paging; the trash is the one lens it refuses (400 RANDOM_NOT_SUPPORTED):
const surprise = await client.creations.random({ smart: 'favorites' });
const anyFox = await client.creations.random({ search: 'fox' });creations.feed pages the cross-user public feed, newest-published first —
what the studio's community wall shows. Rows you don't own hide their prompts;
includeNsfw opts into flagged rows:
const feed = await client.creations.feed({ limit: 48 }); // limit 1..48, default 24
if (feed.hasMore) await client.creations.feed({ limit: 48, page: 2 });like is an idempotent SET, never a toggle — a retry can't double-toggle,
which is what you want in automation. changed is false when the state
already matched. Any live creation is likeable (your own or a public one);
trashed/deleted rows answer 410:
const r = await client.creations.like(gen, true); // { liked: true, likeCount, changed }
await client.creations.like(gen, false); // unlike — same idempotent contractYour liked creations are the activity: 'likes' lens.
clone makes a byte-identical duplicate (images only) with its own CDN
file — trashing either copy never breaks the other. The copy inherits the
original's description, tags and moderation verdict (same bytes, nothing is
re-scored), lands private in the same folder as the original (move it
afterwards to relocate), and costs no credit. It rides the same tier +
storage-quota gates as an upload, and needs the manage scope:
const copy = await client.creations.clone(gen);
copy.isCloned; // true
copy.clonedFromCreationId; // gen's id — chains are flattened: cloning a
// clone still points at the ORIGINALTypical use: keep one master library and fan copies out into per-project
folders, without re-uploading bytes or re-running moderation. Video rows
reject with CLONE_UNSUPPORTED_MEDIA.
await client.creations.trash(gen); // reversible
await client.creations.restore(gen); // undo
// Permanent delete — must be trashed first (else 409 NOT_IN_TRASH)
await client.creations.trash(gen);
await client.creations.delete(gen); // purges the CDN copy toobatch runs one action over up to 1000 ids in a single request — one call
instead of a thousand:
const r = await client.creations.batch('trash', ids);
// { action: 'trash', requested: 812, processed: 810, skipped: 2 }Actions: 'trash', 'untrash', 'delete'. It's owner-only, and rows whose
state doesn't fit the action are counted in skipped rather than failing the
call — delete requires rows to already be in trash, so the safe two-step
purge is batch('trash', ids) then batch('delete', ids).
reorder stamps an explicit drag-order: creationIds[i] gets sort index i
(≤1000 ids; ids you don't own are silently skipped). This is the order the
studio's drag-reorder shows:
await client.creations.reorder([id3, id1, id2]); // → { reordered: 3 }Creations made through the SDK / raw REST / MCP are stamped with a source
('sdk', 'api', 'mcp'; studio creations are 'web') and surface in the
dashboard's Cloud Drive → Activities → SDK / MCP collections, on top of
the usual All / History / Uploads views. The field is on every Creation
row (SDK ≥ 2.0.1):
const page = await client.creations.list({ limit: 50 });
const mine = page.creations.filter(c => c.source === 'sdk');
// Or ask for the collection directly — the same lens the dashboard shows:
const sdkWork = await client.creations.list({ activity: 'sdk' });await client.creations.publish(gen); // → { isPublicShared: true }
await client.creations.unpublish(gen); // → { isPublicShared: false }Only your own generated creations can be published; uploaded source files
cannot. Publishing a trashed creation returns 409 CREATION_IN_TRASH — restore
it first.