Skip to content

Uploads

2DAITeam edited this page Oct 13, 2026 · 6 revisions

Uploads

Bring your own image, GIF, or MP4 (or M4V — the same container, stored as MP4) into your library so it can be used as a generation reference or animated into a video — or an MP3 that becomes the audio reference of a Gen8 Preview video clip. The same moderation pass as the studio runs server-side, so a flagged upload throws NsfwRejectedError.

Requires the generate scope.

Node (from a file path)

const up = await client.uploads.image({ path: './portrait.jpg' });
console.log(up.creationId, up.width, up.height);

Browser (from a Blob / File)

const file = input.files[0];        // <input type="file">
const up = await client.uploads.image({ data: file });

From bytes or base64

await client.uploads.image({ data: myUint8Array, filename: 'ref.png' });
await client.uploads.image({ base64: '...', filename: 'ref.jpg', contentType: 'image/jpeg' });

UploadInput accepts exactly one source: path (Node), data (Uint8Array | ArrayBuffer | Blob), or base64. filename and contentType are optional (inferred when possible).

File it into a folder

By default an upload lands unfiled at the drive root. targetFolderId files it directly into a folder instead — no separate move call, and the duplicate check (below) is scoped to that folder:

const folder = await client.folders.create({ title: 'Refs' });
const up = await client.uploads.image({ path: './ref.jpg', targetFolderId: folder.folderId });
up.folderId;   // folder.folderId

The folder must be one you can write to — your own, or one shared with you with editor rights (system folders are excluded). Anything else answers 403 INVALID_FOLDER and the upload is discarded, not filed at the root.

Record an edit (lineage)

If the file you're uploading is an edit of a creation you already own — a crop you made locally, a background you erased — say so, and the platform links the two:

await client.uploads.image({ path: './cropped.png', croppedFromCreationId: original.creationId });
await client.uploads.image({ path: './cutout.png', erasedFromCreationId: original.creationId });

The two fields are mutually exclusive (an upload is one kind of edit), and both are images-only, on creations you own. The new creation counts as lineage of the original — it shows up in creations.list({ usedRef: original }) — and is filed with the studio's crop / erase edits (crops feed the crop smart collection, erase cut-outs with real transparency feed alpha).

Use it as a reference

const up = await client.uploads.image({ path: './face.jpg' });
await client.generate.imageWithRefs({
  tool: 'character-ref',
  prompt: 'wearing a wizard hat',
  refCreationIds: [up.creationId],
});

Uploaded source files can't be published to the public feed — only your own generated creations can.

Video uploads (MP4 / M4V)

An .mp4 or .m4v clip (the same ISO BMFF container — the platform stores and serves it as .mp4, outputExtension: '.mp4', mediaKind: 'video', duration in seconds) needs an account allowed to upload videos (403 VIDEOS_NOT_ALLOWED otherwise). A file the platform cannot read is refused with 400 INVALID_MEDIA; a DRM-protected one — an iTunes / FairPlay purchase, a CENC-encrypted MP4 — with 400 PROTECTED_MEDIA. Both are ValidationErrors: the file is the problem, not the service, so do not retry them.

const clip = await client.uploads.image({ path: './clip.m4v' });   // contentType inferred: video/mp4
clip.mediaKind;   // 'video'

An audio reference for Gen8 Preview video

uploads.audio() takes an MP3 — 5 minutes max (400 AUDIO_TOO_LONG), Holder tier and up (403 AUDIO_NOT_ALLOWED) — and returns an audio creation (mediaKind: 'audio', duration in seconds, no pixels so the file name and length label it). Pass its id to generate.video with videoModel: 'next': audioUse: 'music' lays it under the clip as soundtrack / sound design, 'voice' makes it the voice sample the speaking character follows. The audio counts as one more reference for the price.

const track = await client.uploads.audio({ path: './theme.mp3' });       // Node
const track2 = await client.uploads.audio({ data: mp3Blob, filename: 'theme.mp3' }); // Browser
const clip = await client.generate.video({
  videoModel: 'next',
  inputCreationId: frame.creationId,
  prompt: 'the band walks on stage as the intro swells',
  audioCreationId: track.creationId,
  audioUse: 'music',
  duration: 8,
});

Duplicate detection

The server de-duplicates uploads per folder by content hash: sending the exact same pixels into the same folder returns 409 DUPLICATE_IN_FOLDER (a ValidationError). Re-uploading the same file into a different folder is fine, as is uploading different bytes. With targetFolderId, the check runs against that folder.

checkDuplicate asks the question before you pay for the transfer — an md5 of the file bytes instead of the file itself:

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

const bytes = await readFile('./ref.jpg');
const md5 = createHash('md5').update(bytes).digest('hex');

const probe = await client.uploads.checkDuplicate(md5, folder.folderId);
// { duplicate: true, existingCreationId: '…', folderId: '…' } — or duplicate: false
if (!probe.duplicate) {
  await client.uploads.image({ data: bytes, filename: 'ref.jpg', targetFolderId: folder.folderId });
}

Omit folderId to check against the drive root. The probe only needs the read scope, and it is advisory: the upload itself still re-checks, so a concurrent upload can race your false — treat it as a hint that saves the round-trip, not a lock.

Clone this wiki locally