Skip to content

Recipes

2DAITeam edited this page Oct 14, 2026 · 7 revisions

Recipes

End-to-end patterns that combine several calls.

Generate a batch, organised into a folder

const folder = await client.folders.create({ title: 'Mascots' });

const prompts = ['a red fox mascot', 'a blue owl mascot', 'a green frog mascot'];
const results = await Promise.all(
  prompts.map((prompt) => client.generate.image({ prompt, aspectRatio: '1:1' })),
);

for (const gen of results) {
  await client.creations.move(gen, folder.folderId);
  await client.download(gen, { savePath: `./mascots/${gen.creationId}` });
}

An agent tool: prompt → image on the user's account

async function makeImage(prompt: string): Promise<{ url: string; costUsd?: number }> {
  const gen = await client.generate.image({ prompt });
  return { url: gen.downloadUrl!, costUsd: gen.costUsd };
}

Check headroom before spending

const me = await client.me();
const remaining = me.key.spendLimitUsd == null
  ? me.creditUsd
  : Math.min(me.creditUsd, me.key.spendLimitUsd - me.key.spentUsd);

if (remaining < 0.1) throw new Error('Low balance');
await client.generate.image({ prompt: '…' });

Non-blocking fan-out, then collect

const tickets = await Promise.all(
  prompts.map((p) => client.generate.image({ prompt: p }, { wait: false })),
);

const finished = await Promise.all(
  tickets.map((t) => client.queue.waitFor(t.queueId, { timeoutMs: 300_000 })),
);

const done = finished.filter((s) => s.status === 'completed');

Upload a reference, then a face-consistent series

const ref = await client.uploads.image({ path: './hero.jpg' });

const scenes = ['on a beach', 'in a snowy forest', 'on a city rooftop'];
for (const scene of scenes) {
  const gen = await client.generate.imageWithRefs({
    tool: 'character-ref',
    prompt: scene,
    refCreationIds: [ref.creationId],
  });
  await client.download(gen, { savePath: `./series/${scene.replace(/\W+/g, '_')}` });
}

Publish your best, mirror to a local gallery

const page = await client.creations.list({ limit: 100 });
for (const c of page.creations) {
  if (!c.isUploaded) await client.creations.publish(c);        // publish generated work
  await client.cdn.download(c, { savePath: `./gallery/${c.creationId}` });
}

Empty the trash

One batch call replaces the per-row loop — up to 1000 ids at a time, and delete only touches rows already in trash (anything else is skipped):

let page = await client.creations.list({ trashed: true, limit: 100 });
while (page.creations.length) {
  await client.creations.batch('delete', page.creations.map((c) => c.creationId));
  page = await client.creations.list({ trashed: true, limit: 100 });
}

Auto-file uploads into a folder

targetFolderId files each upload as it lands, and checkDuplicate skips files the folder already has — without paying for the transfer:

import { createHash } from 'node:crypto';
import { readFile } from 'node:fs/promises';

const folder = await client.folders.create({ title: 'Inbox' });

for (const path of files) {
  const bytes = await readFile(path);
  const md5 = createHash('md5').update(bytes).digest('hex');
  const probe = await client.uploads.checkDuplicate(md5, folder.folderId);
  if (probe.duplicate) continue;
  await client.uploads.image({ data: bytes, filename: path, targetFolderId: folder.folderId });
}

Nightly stats digest

Everything here is read-only — safe to run on a cron with a read-scoped key (add the finance scope if you want the credit line):

const [o, g] = await Promise.all([
  client.stats.overview(),
  client.stats.generations({ days: 30 }),
]);

const topTool = g.byTool[0];
console.log(
  `${o.counters.totalCreations} creations (+${o.counters.weekCreations} this week), ` +
  `streak ${o.streak.days}d\n` +
  `30d: ${g.totals.count} generations, $${g.totals.costUsd.toFixed(2)} ` +
  `(machine ${g.bySource.machine.count} / studio ${g.bySource.studio.count})` +
  (topTool ? `\nbusiest tool: ${topTool.toolKind} (${topTool.count})` : '')
);

Page through a large drive

list pages by date cursor — pass the previous page's nextBeforeDate:

let cursor: string | undefined;
do {
  const page = await client.creations.list({ limit: 50, beforeDate: cursor });
  handle(page.creations);
  cursor = page.nextBeforeDate;
} while (cursor);

A self-cleaning integration test

Pattern for CI suites that generate on a real account and leave nothing behind — settle fire-and-forget tickets before purging, then purge in reverse dependency order:

const made: string[] = [];   // creationIds this run produced
const tickets: string[] = []; // queueIds still in flight

afterAll(async () => {
  for (const q of tickets) {
    try {
      const st = await client.queue.waitFor(q, { timeoutMs: 120_000 });
      if (st.creationId) made.push(st.creationId);
    } catch { /* failed → refunded, nothing landed */ }
  }
  for (const id of made.reverse()) {
    try { await client.creations.trash(id); await client.creations.delete(id); }
    catch { /* best-effort */ }
  }
});

Cancel a slow generation

const ctrl = new AbortController();
const t = setTimeout(() => ctrl.abort(), 20_000);
try {
  const gen = await client.generate.image({ prompt: '…', quality: 'max' }, { signal: ctrl.signal });
} catch (err) {
  // TimeoutError / aborted — the job may still finish server-side; find it via creations.list
} finally {
  clearTimeout(t);
}

A Gen8 Preview video clip with a cast of references

// Gen8 Preview video: one opening frame, a cast that stays consistent, sound included.
const me = await client.me();
const open = (me.videoDurationsNext ?? []).filter(d => !d.locked).map(d => d.value); // e.g. [1, 5, 8, 10]

const hero = await client.generate.image({ prompt: 'a kite surfer in a red wetsuit, portrait, golden hour', aspectRatio: '3:4' });
const beach = await client.generate.image({ prompt: 'an empty beach at golden hour, waves, wide shot', aspectRatio: '16:9' });

const clip = await client.generate.video({
  videoModel: 'next',
  inputCreationId: beach.creationId,          // the opening frame
  refCreationIds: [hero.creationId],          // the surfer, kept recognisable
  prompt: 'Opening on the empty beach. The surfer in the red wetsuit lands the kite, runs out of the water and waves at the camera; gulls and surf in the sound.',
  duration: Math.max(...open),                // the longest length open to this account
  aspectRatio: 'auto',
});
await client.download(clip, { savePath: './landing' }); // → ./landing.mp4

// The same clip scored with your own track (an MP3 of yours, 5 min max):
const track = await client.uploads.audio({ path: './surf-theme.mp3' });
const scored = await client.generate.video({
  videoModel: 'next',
  inputCreationId: beach.creationId,
  refCreationIds: [hero.creationId],
  prompt: 'Opening on the empty beach; the surfer lands the kite as the chorus hits and waves at the camera.',
  audioCreationId: track.creationId,          // counts as one more reference
  audioUse: 'music',                          // or 'voice' for a voice sample
  duration: 8,
});

Consistent visuals from 6 references (Gen8 Preview images)

// Gen8 Preview images: a product, a model, a place, an outfit, a prop and a logo — six references kept recognisable in one picture.
const me = await client.me();
if (!me.canUseImageNext) throw new Error('Gen8 Preview images open at Holder for this account');
const open = (me.qualities?.imageNext ?? []).filter(q => q.allowed).map(q => q.id);   // e.g. ['fast', 'normal', 'high', 'max']

const refs = await Promise.all([
  'a matte black wireless headphone, product shot on white',
  'a woman with short silver hair and round glasses, studio portrait',
  'a rooftop café at dusk, string lights, city skyline',
  'a cropped olive-green bomber jacket on a mannequin, product shot on white',
  'a vintage turquoise vinyl record player, product shot on white',
  'a minimalist round logo of a white sound wave on a black disc, flat vector',
].map(prompt => client.generate.image({ prompt, quality: 'max' })));

const campaign = await client.generate.image({
  imageModel: 'next',
  refCreationIds: refs.map(r => r.creationId),             // all six; each adds a surcharge
  prompt: 'The woman with short silver hair and round glasses wears the matte black wireless headphones and the cropped olive-green bomber jacket, seated at the rooftop café under string lights at dusk next to the vintage turquoise vinyl record player; a poster on the wall shows the round white sound-wave logo above the words "SOUND OF THE CITY" in bold white letters.',   // name each reference by what it shows, never by its position
  quality: open.includes('max') ? 'max' : open[open.length - 1],
  aspectRatio: '3:2',
});
await client.download(campaign, { savePath: './campaign' });   // → ./campaign.jpg
// Re-run the same picture on Gen8 Preview images with a fresh seed:
const variant = await client.generate.similar(campaign);

Clone this wiki locally