Skip to content

Cloud Drive

2DAITeam edited this page Sep 2, 2026 · 6 revisions

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 the read scope.
  • Organising (folders, groups, move, like, reorder, batch, trash, restore, delete) needs manage.
  • Publishing / unpublishing needs publish.

Folders (collections)

// 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 });

Folder groups

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 deleted

Move 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.

Moving creations

// 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).

Browsing the drive

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 });

Search, sort and filters

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.

Pagination

// 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.

Random pick

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' });

The public feed

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 });

Likes

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 contract

Your liked creations are the activity: 'likes' lens.

Cloning

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 ORIGINAL

Typical 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.

Trash, restore, delete

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 too

Bulk actions

batch 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

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 }

Where API creations land (source)

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' });

Publish / unpublish

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.

Clone this wiki locally