2dai-cloud-sdk

中文文档
3.2.0发布于 2026/11/03MITTypeScriptESM + CJS零依赖构建来源已验证近 30 天下载 1838

Typed JavaScript/TypeScript client for the 2DAI generation API — generate images & video, upload references, and download results with an API key.

原始使用文档
文档目录18 节
  1. Gen8 on the default channels
  2. Get a key
  3. Quickstart
  4. Generating
  5. Uploading your own media
  6. Downloading
  7. Cloud drive
  8. Browsing your library
  9. Organising
  10. Account & history
  11. Stats
  12. Wallet data (read-only)
  13. Error handling
  14. The 2DAI stack
  15. Not on JavaScript? Use the REST API
  16. Configuration
  17. License
  18. Community & links

2dai-cloud-sdk

npm

Typed JavaScript/TypeScript client for the 2DAI generation API. Generate images and video, upload references, browse and organise your cloud drive, and download results — all with an API key. Node 18+ and modern browsers. Zero runtime dependencies.

Not on JavaScript? The same platform is a plain REST API — jump to Use the REST API.

npm install 2dai-cloud-sdk

Gen8 on the default channels

generate.image and generate.video run the Gen8 engines on their default channel; me().modelLabels names the engine behind each channel (today Gen8.1), and every rule of a channel follows that engine:

  • Images: up to 6 refCreationIds kept natively, legible text in the picture, long literal prompts honoured, negativePrompt applied at every preset.
  • Video: clips with sound at 24 fps, from a first frame (inputCreationId) plus up to 6 refCreationIds (characters, props, places), an optional aspectRatio and an optional audio reference (uploads.audio() an MP3, then pass its id as audioCreationId, audioUse: 'music' for a soundtrack or 'voice' for a voice sample). Lengths are exact seconds (1, 5, 8, 10, 12, 15, 17, 20, opened by tier); video opens at Explorer.
  • Presets and lengths depend on the tier: read me().qualities and me().videoDurations instead of guessing.

next is kept for the next engine. Until 2026-11-17 it serves the same engine as the default channel; after that it answers 400 VIDEO_MODEL_UNAVAILABLE / IMAGE_MODEL_UNAVAILABLE, uncharged, until an engine opens it (see me().modelChannels). Leave videoModel / imageModel unset. Coming from 3.1.x: see the CHANGELOG.

Get a key

Sign in at 2dai.io (any active account works — no plan required), then in the dashboard open Integrations → API keys → + New key. Keys look like 2dai_sk_…, carry scopes (read, generate, …), and can have an optional spend cap. The secret is shown once — store it somewhere safe. API generations spend your account credit exactly like the studio. Full walkthrough: Getting Started.

Quickstart

import { Client } from '2dai-cloud-sdk';

const client = new Client({ apiKey: process.env.TWODAI_API_KEY! });

// Generates, waits for completion, and returns the finished creation.
const gen = await client.generate.image({ prompt: 'a red origami fox', aspectRatio: '1:1' });
console.log(gen.creationId, gen.downloadUrl);

// Save the actual bytes to disk (Node).
await client.download(gen, { savePath: './fox' }); // → ./fox.jpg

Generating

generate.* submits to an async queue. By default it polls to completion and returns the finished GenerationResult. Pass { wait: false } to get the queue ticket immediately and poll yourself.

// One-shot (default): resolves when the image is ready.
const img = await client.generate.image({
  prompt: 'a neon city street, rain',
  aspectRatio: '16:9', // or 'auto' — the server picks the best ratio from the prompt
  // style + quality default to 'auto' (the server picks by your tier);
  // me().qualities lists the presets open to your tier and flags the recommended one
});

// `enhanced` rewrites your prompt with TIXI, the 2DAI prompt agent, before it
// reaches the image model — it fills in camera, lighting, composition and style
// from a short brief. ~2s extra, no extra credit. Your text stays in `.prompt`;
// TIXI's version is in `.raw.finalPrompt`.
const rich = await client.generate.image({ prompt: 'a warrior', enhanced: true });

// Up to 6 references kept natively, legible text, long literal prompts honoured.
const poster = await client.generate.image({
  prompt: 'a concert poster titled "NEON TIDE": the singer with the red braids on the neon-lit festival stage, bold white typography',
  refCreationIds: [singerId, venueId],      // optional, ≤6 (each adds a surcharge)
  quality: 'auto',                          // or an allowed id of me().qualities.image ('max' opens at Holder)
});

// Non-blocking: get a ticket now, poll later.
const { queueId } = await client.generate.image({ prompt: '…' }, { wait: false });
const state = await client.queue.waitFor(queueId, { timeoutMs: 300_000 });

// Reference tools (character-ref / style-transfer / smart-edit), on the Gen8 image engine:
const ref = await client.generate.imageWithRefs({
  tool: 'character-ref',         // 1–6 shots of one character; face photos lock the exact same face
  prompt: 'as an astronaut',
  refCreationIds: [gen.creationId],
});

// Smart edit: refs[0] is the image to EDIT, the prompt is the instruction.
const edited = await client.generate.imageWithRefs({
  tool: 'smart-edit',
  prompt: 'replace the background with a rainy neon street',
  refCreationIds: [gen.creationId],
});

// Artist Painter: paint your subject as a new work in a curated
// artistic style. `artisticStyles.list()` gives the ids; omit `artisticStyleId` for 'auto'.
const styles = await client.artisticStyles.list();
const painting = await client.generate.artisticStyle({
  prompt: 'a lighthouse on a stormy coast',
  artisticStyleId: styles[0]?.artisticStyleId,          // or 'auto'
  refCreationIds: [gen.creationId],        // optional subject images (≤6)
});

// Video from an existing still, with sound. `duration` is one of me().videoDurations
// (exact seconds, tier lock resolved). Every video style except 'raw' runs TIXI on your
// prompt (the style's guidance only exists through that rewrite); 'raw' sends it
// verbatim unless enhanced: true. An empty prompt on 'raw' or 'auto' runs the 'action'
// style, exactly as if you had picked it.
const vid = await client.generate.video({
  prompt: 'gentle camera push-in',
  inputCreationId: gen.creationId,
  duration: 5,
  style: 'raw',        // verbatim — add `enhanced: true` to let TIXI write the brief
});

// A first frame plus up to 6 references, a ratio and a longer clip. Clips render for
// minutes: the SDK waits up to 20 min for a video (set timeoutMs, or wait: false).
const scene = await client.generate.video({
  prompt: 'the kite surfer lands, waves at the camera, the crowd cheers',
  inputCreationId: gen.creationId,          // first frame
  refCreationIds: [characterId, placeId],   // optional, ≤6 (each adds a surcharge)
  duration: 8,                              // one of me().videoDurations (8 s opens at Holder)
  aspectRatio: '16:9',                      // 'auto' = the first frame's ratio
  style: 'raw', enhanced: true,             // your idea → TIXI writes the brief (shots, pictures, sound)
});

// With an audio reference: your MP3 as the soundtrack ('music') or as a voice
// sample the speaking character follows ('voice'). Counts as one more reference
// for the price.
const track = await client.uploads.audio({ path: './theme.mp3' });
const scored = await client.generate.video({
  prompt: 'the band walks on stage as the intro swells',
  inputCreationId: gen.creationId,
  audioCreationId: track.creationId,
  audioUse: 'music',
  duration: 5,
});

// Wallpaper-resize: expand a creation into a wallpaper dimension. It runs on its own
// engine: quality is always Ultra, the dimension drives the price, up to 3 extra references.
const wall = await client.generate.wallpaper({
  inputCreationId: gen.creationId,
  dimension: 'widescreen',   // 'standard' | 'photo' | 'widescreen' | 'ultrawide'
});

Each tool caps its references (6 image · 6 character-ref · 6 style-transfer · 6 smart-edit · 6 artistic-style · 3 wallpaper · video: the first frame plus 6) — exceeding a cap rejects with TOO_MANY_REFS before anything is charged. A ticket that is still pending can be stopped with client.queue.cancel(queueId); the charge is refunded. Details: Generating.

Uploading your own media

Bring an image/gif/mp4 (an .m4v is accepted and stored as mp4) into your library so it can be used as a reference or animated, or an MP3 that becomes the audio reference of a video clip. The same moderation pass as the studio runs server-side.

const up = await client.uploads.image({ path: './portrait.jpg' });      // Node
const up2 = await client.uploads.image({ data: fileBlob });             // Browser
await client.generate.imageWithRefs({ tool: 'character-ref', prompt: 'in a suit', refCreationIds: [up.creationId] });

// An MP3 (5 min max, from Explorer, like video) as a clip's audio reference — see generate.video above.
const track = await client.uploads.audio({ path: './theme.mp3' });

// File it straight into a folder, and skip re-uploads with the md5 pre-flight:
const probe = await client.uploads.checkDuplicate(md5Hex, folder.folderId);
if (!probe.duplicate) {
  await client.uploads.image({ path: './portrait.jpg', targetFolderId: folder.folderId });
}

targetFolderId files the upload into a folder you can write to (403 INVALID_FOLDER otherwise). croppedFromCreationId / erasedFromCreationId record the upload as a crop or erase edit of a creation you own. Details: Uploads.

Downloading

client.cdn authenticates with your key, so it works for private creations too. Accepts a CDN id, a Creation, a QueueState, or a generation result.

await client.cdn.download(gen, { savePath: './out' });   // Node → './out.jpg' (ext inferred)
const asset = await client.cdn.fetch(gen);               // { bytes, contentType, ext }
const blob = await client.cdn.toBlob(gen);               // Browser
const dataUrl = await client.cdn.toDataUrl(gen);         // <img src={dataUrl} />

// The CDN can resize before it sends: `maxSide` scales the longest edge and
// keeps the aspect ratio, so one call covers portrait and landscape.
const preview = await client.cdn.toDataUrl(gen, { maxSide: 512 });   // 1164 kB → 132 kB
await client.cdn.download(gen, { maxSide: 256, savePath: './thumb' }); // composes with savePath

maxSide is what you want for previews and thumbnails; width / height target one axis specifically and may crop. Details: Downloading.

Cloud drive

Organise your library from code — folders, moving, trash, publishing (scopes: manage for organising, publish for the public feed).

const folder = await client.folders.create({ title: 'Brand assets' });
await client.creations.move(gen, folder.folderId);   // or move(gen, 'root')
await client.creations.publish(gen);                 // generated work only
await client.creations.trash(gen);                   // reversible
await client.creations.restore(gen);
await client.creations.trash(gen); await client.creations.delete(gen); // permanent (trash first)
await client.folders.delete(folder.folderId, { trashContents: true });

Browsing your library

creations.list speaks the same lenses and filters as the studio's cloud drive — one mode per call (a folder, an activity view, a smart collection, or a folder shared with you), plus filters that compose with any of them.

await client.creations.list({ folderId: folder.folderId });   // or 'root', or { trashed: true }
await client.creations.list({ activity: 'uploaded' });        // all|history|uploaded|likes|public|trash|sdk|mcp|portfolio
await client.creations.list({ smart: 'faces' });              // faces|videos|crop|alpha|favorites

// Filters compose with any lens: whole-word search (trailing * = prefix),
// sort, aspect-ratio buckets, date range, the studio's Likes and Type filters…
await client.creations.list({ search: 'red fox*', sort: 'size-desc', ratioFilter: 'portrait' });
await client.creations.list({ folderId: folder.folderId, likeFilter: 'liked', mediaKind: 'video' });

// ONE uniformly-random creation from any lens (images and videos only), same filters:
const surprise = await client.creations.random({ smart: 'favorites', mediaKind: 'image', blockNsfw: true });

// The cross-user public feed:
const feed = await client.creations.feed({ limit: 48 });

Paginate with nextBeforeDate (limit 1..100) or 1-based page. Rows carry description, folderId, inTrash, isPublicShared, likes, isLiked and more. Details: Cloud Drive.

Organising

await client.creations.like(gen, true);              // idempotent SET, retry-safe
await client.creations.batch('trash', ids);          // bulk trash/untrash/delete, ≤1000 ids
await client.creations.reorder([id1, id2, id3]);     // stamp drag-reorder order

// Sidebar folder groups + folder metadata:
const group = await client.folders.groups.create('Client work');
await client.folders.update(folder.folderId, {
  groupId: group.groupId,            // null detaches
  posterCreationId: gen.creationId,  // null clears
  isFavorite: true,
});

Deleting a group only detaches its folders — nothing else is deleted.

Account & history

const me = await client.me();
console.log(me.creditUsd, me.tier, me.key.scopes);
console.log(me.promptMaxChars, me.promptMaxCharsNext); // prompt cap for this tier (1,500 → 20,000), and the tier that raises it
console.log(me.modelLabels, me.videoDurations, me.qualities); // engine per channel, lengths and presets open to this tier

const page = await client.creations.list({ limit: 20 });
const one = await client.creations.get(page.creations[0].creationId);

const state = await client.queue.get(queueId);       // one-shot status read
const url = client.cdn.url(one);                      // raw CDN url (public creations)

Stats

Read-only account analytics (scope read):

const o = await client.stats.overview();               // storage, counters, streak, per-tool recents
const g = await client.stats.generations({ days: 30 }); // volume + spend by day / tool / source
const t = await client.stats.top();                    // most-used refs (with suggested config), styles, keywords

Wallet data (read-only)

The finance namespace reads your wallet: balance, tier, staking lock, transactions, balance/credit history. It needs a key minted with the opt-in finance scope (off by default) — and it is strictly read-only: no deposit, withdraw, swap or lock operation exists on the API-key surface, so a finance key can see numbers but can never move funds.

const w = await client.finance.wallet();          // tokens, creditUsd, tier, pending flags
const tx = await client.finance.transactions({ limit: 20 });
const price = await client.finance.tokenPrice();  // cached $2DAI quote — only needs `read`

Depth: Stats & Wallet.

Error handling

Every failure throws a typed error you can branch on:

import { InsufficientCreditError, SpendLimitError, NsfwRejectedError, ApiError } from '2dai-cloud-sdk';

try {
  await client.generate.image({ prompt: '…' });
} catch (err) {
  if (err instanceof InsufficientCreditError) console.log('Top up:', err.deficitUsd);
  else if (err instanceof SpendLimitError) console.log('Key cap hit at', err.spendLimitUsd);
  else if (err instanceof NsfwRejectedError) console.log('Blocked by moderation');
  else if (err instanceof ApiError) console.log(err.code, err.httpStatus);
  else throw err;
}

Other errors: AuthError, ScopeError, TierError, QueueLimitError, RateLimitError, ValidationError (with DurationError for 400 INVALID_DURATION, whose allowed lists the lengths of the channel), NotFoundError, GenerationFailedError, TimeoutError, GenerationPausedError (503 GENERATION_PAUSED, a short maintenance: retry later), KeyPolicyQualityError / KeyPolicyDurationError (403, the key's own caps: maxQuality and kind, maxVideoDurationSec). All extend ApiError (.code, .httpStatus, .details). A GenerationFailedError (and a failed QueueState) also carries errorCode to branch on (CAPACITY_FULL, EXPIRED, GENERATION_TIMEOUT, …) and errorMessage, a readable sentence you can show as is. A TierError names the tier that opens what was asked (requiredTier, requiredTierName) and ships a ready sentence in message (for example "… opens at Holder. Upgrade your tier…"); nothing was charged.

A content-rated result is not an error: a creation with nsfwFlagged: true (nsfwRate 0.8 or 0.99) completed and was charged, but it stays masked in the owner's drive, is left out of public feeds and cannot be published (NSFW_NOT_PUBLISHABLE). Hand your user the viewUrl to review it rather than retrying. Only NSFW_MAX_EXCEEDED (platform ceiling) discards the output — nothing is charged, reword the prompt. Details: Content rating.

The 2DAI stack

2DAI runs its own models, and unlike others, we don't resell model access. All our models originate from the open-source community or our own R&D — we then disassemble, modify, fine-tune and optimize them to align with our legacy and 2DAI❤️ART lines. They also run on our own private cloud network.

me().modelChannels / me().modelLabels name the engine behind each channel (videoModel / imageModel); every rule of a channel follows that engine. The next channels are kept for the next engine.

The prompt rewriter (the "TIXI" pass that expands short prompts before generation) is our own in-house LLM — same rule: not re-sold third-party model access. Depth: About the Models.

Not on JavaScript? Use the REST API

Everything below is plain HTTP — the SDK is a convenience layer, not a gate. Base URL https://dapp.2dai.io:444/v1, bearer auth, JSON in and out. Generation is asynchronous: submit, then poll.

# 1 · submit → returns a queueId
curl -X POST https://dapp.2dai.io:444/v1/generate/image \
  -H "Authorization: Bearer $TWODAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"prompt":"a lighthouse at dusk"}'

# 2 · poll until status is completed, then fetch downloadUrl with the same bearer
curl https://dapp.2dai.io:444/v1/queue/$QUEUE_ID \
  -H "Authorization: Bearer $TWODAI_API_KEY"
MethodEndpointScopeWhat
GET/v1/mereadAccount, credit, key scopes
POST/v1/generate/imagegenerateText → image
POST/v1/generate/image-with-refsgenerateFace / character ref, style transfer, smart edit
POST/v1/generate/wallpapergenerateWallpaper-resize (priced by dimension)
POST/v1/generate/videogenerateStill → video
POST/v1/uploadsgenerateUpload media (multipart)
GET/v1/uploads/check-duplicatereadmd5 pre-flight dedup probe
GET/v1/queue/:queueIdreadPoll a generation
POST/v1/queue/:queueId/cancelgenerateCancel a pending generation (refunds)
GET/v1/creationsreadList / search your drive (lenses + filters)
GET/v1/creations/:idreadOne creation, full detail
GET/v1/feedreadThe cross-user public feed
POST/v1/creations/:id/move | /trash | /restore | /likemanageOrganise
POST/v1/creations/reorder | /batchmanageReorder / bulk actions
DELETE/v1/creations/:idmanagePermanent delete (trash first)
POST/v1/creations/:id/publish | /unpublishpublishPublic feed
GET/POST/PATCH/DELETE/v1/foldersread / manageFolders
GET/POST/PATCH/DELETE/v1/folder-groupsread / manageSidebar folder groups
GET/v1/stats/overview | /generations | /topreadAccount analytics
GET/v1/wallet*, /v1/account/tierfinanceRead-only wallet data
GET/v1/tiers, /v1/token/pricereadTier catalogue, cached token quote
GET/cdn/file/:cdnIdreadDownload bytes (?s=512 for a preview)

Full request/response shapes, every error code and the limits are documented in the REST API reference.

Configuration

new Client({
  apiKey: '2dai_sk_…',   // required
  baseUrl,               // default 'https://dapp.2dai.io:444'
  timeoutMs,             // per-request network timeout, default 60000
  maxRetries,            // idempotent GET retries on 429/5xx, default 2
  fetch,                 // inject a custom fetch
  integration,           // 'sdk' (default) | 'mcp' — official 2DAI integrations
  integrationVersion,    // that integration's own semver, e.g. '1.4.0'
});

integration is a closed set rather than a free-form User-Agent, and you should leave it alone: the server stamps each creation's source from it, and the dashboard reads source to fill its SDK / MCP collections and label where a row came from. 'mcp' is reserved for the official 2DAI MCP server; anything you build reports as sdk. integrationVersion must be a real 1.2.3; a malformed value is replaced by 0.0.0 rather than omitted, so the token still attributes.

Generation submits are never auto-retried (they may charge); the server itself de-duplicates an identical submit within ~5 s (409 IDEMPOTENT_RETRY), and a hex clientToken you pass sets that window explicitly.

License

MIT

版本与发布记录

2dai-cloud-sdk 共发布 48 个版本,首次发布于 2026/01/07;过去 12 个月发布了 48 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
3.2.0latest2026/11/034 天前
3.1.32026/11/017 天前
3.1.22026/10/309 天前
3.1.12026/10/299 天前
3.1.02026/10/2118 天前
3.0.02026/10/1524 天前

关于 2dai-cloud-sdk 的常见问题

2dai-cloud-sdk 是什么?

Typed JavaScript/TypeScript client for the 2DAI generation API — generate images & video, upload references, and download results with an API key.

如何安装 2dai-cloud-sdk?

在项目目录运行 npm install 2dai-cloud-sdk,或使用 pnpm add / yarn add / bun add。

2dai-cloud-sdk 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

2dai-cloud-sdk 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

2dai-cloud-sdk 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=18。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

2dai-cloud-sdk 可以商用吗?

2dai-cloud-sdk 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

2dai-cloud-sdk 的下载量有多少?

根据 npm 官方下载统计,2dai-cloud-sdk 在 2026/10/04 – 2026/11/02 这 30 天内被下载 1,838 次,日均约 61 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

2dai-cloud-sdk 还在维护吗?

最新版本 3.2.0 发布于 2026/11/03(4 天前);至今共发布 48 个版本,其中过去 12 个月发布了 48 个;首次发布于 2026/01/07。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。