Catalog npm
Back to packageRELEASE DETAILS

2.5.2

npm

2dai-cloud-sdk

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

2daiagentsaiapi-clientclaudegenerative-aiimage-generationimage-to-videomcpsdktext-to-imagetypescriptvideo-generationagenticautonomousllm
All keywords (18)
multimodalvision
Published2 Sept 2026
Direct dependencies3
Transitive dependenciesNot calculated

README

Current package documentation

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

New — Gen8 Preview images

client.generate.image({ imageModel: 'next', ... }) runs Gen8 Preview images, the preview of 2DAI's next image engine: up to 6 refCreationIds kept natively (3 on the default model), legible text in the picture, long literal prompts honoured, the same quality ids as today. The channel opens at Holder — read me().canUseImageNext and me().qualities.imageNext instead of guessing; a next image is priced on render time (roughly four to seven times the same preset on Gen 7.2), and every reference adds a surcharge on both models (costUsd on the ticket is the truth).

Gen8 Preview video

client.generate.video({ videoModel: 'next', ... }) runs Gen8 Preview video, the preview of 2DAI's next video engine: clips come out with sound, from a first frame (inputCreationId) plus up to 6 refCreationIds (characters, props and places kept consistent), with an optional aspectRatio. Lengths and presets follow the account — fast quality and 1, 5, 8 or 10 s from Holder, every preset and 12, 15, 17 or 20 s at Founder: read me().videoDurationsNext and me().qualities.videoNext instead of guessing. Since 2.7.0 the clip can follow an audio reference too: uploads.audio() an MP3, then pass its id as audioCreationId (audioUse: 'music' for a soundtrack, 'voice' for a voice sample) — and TIXI writes the full scenario around it.

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);
  // recommended: quality 'max' for images, 'ultra' at 5 s for video, 'ultimate' = highest resolution
});

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

// Gen8 Preview images, the new image engine (Holder+): up to 6 references kept natively,
// legible text, long literal prompts. Same quality ids; me().qualities.imageNext
// lists what the account may submit.
const poster = await client.generate.image({
  imageModel: 'next',
  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: 'max',
});

// 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 (silent, 5, 6 or 7 s — 7 s needs Supporter+).
// 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.
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 scenario
});

// Gen8 Preview video, the new video engine (clips WITH sound): a first frame plus up to 6
// references. Lengths and presets follow the account (me().videoDurationsNext /
// qualities): Fast and 1-10 s from Holder, up to 20 s at Founder. The SDK waits up to 20 min here.
const next = await client.generate.video({
  videoModel: 'next',
  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: 10,                             // one of me().videoDurationsNext
  aspectRatio: '16:9',                      // 'auto' = the first frame's ratio
  style: 'raw', enhanced: true,             // your idea → TIXI writes the Gen8 Preview video brief (shots, pictures, sound)
});

// Gen8 Preview video 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({
  videoModel: 'next',
  prompt: 'the band walks on stage as the intro swells',
  inputCreationId: gen.creationId,
  audioCreationId: track.creationId,
  audioUse: 'music',
  duration: 8,
});

// Wallpaper-resize: expand a creation into a wallpaper dimension.
// Quality is always Ultra; the dimension drives the price.
const wall = await client.generate.wallpaper({
  inputCreationId: gen.creationId,
  dimension: 'widescreen',   // 'standard' | 'photo' | 'widescreen' | 'ultrawide'
});

Each tool caps its references (3 image, 6 on imageModel: 'next' · 6 face/character · 3 style-transfer · 4 smart-edit · 3 wallpaper · 7 Gen8 Preview video pictures) — 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 Gen8 Preview 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, Holder tier and up) for Gen8 Preview video — 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…
await client.creations.list({ search: 'red fox*', sort: 'size-desc', ratioFilter: 'portrait' });

// ONE uniformly-random creation from any lens:
const surprise = await client.creations.random({ smart: 'favorites' });

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

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, NotFoundError, GenerationFailedError, TimeoutError. 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 ("Gen8 Preview images open 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

We currently use our Gen 7.2 model, 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.

The Gen8 generation is rolling out as two previews behind the next channels: Gen8 Preview video (videoModel: 'next', clips with sound) and Gen8 Preview images (imageModel: 'next', 6 references). Same rule, same cloud; me().modelChannels names the engine behind each channel.

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

Downloads & popularity

Awaiting observations

No metric observations yet

Download counts and popularity will appear here as the collector receives them.

Declared dependencies 3

Native requirements
3 direct targetsNot calculated transitive count
Package / targetRequirementRelationship
tsup
Environment
{
  "local_name": "tsup",
  "alias": false,
  "peer_metadata": null
}
^8.3.0development
typescript
Environment
{
  "local_name": "typescript",
  "alias": false,
  "peer_metadata": null
}
^5.5.0development
@types/node
Environment
{
  "local_name": "@types/node",
  "alias": false,
  "peer_metadata": null
}
^20.14.0development

Distribution files

1 collected
Distribution 1

Source distribution

Source file
Checksums from source
{
  "shasum": "4e5d40999340a2850fb509723eebc5472cd7bf70",
  "tarball": "https://registry.npmjs.org/2dai-cloud-sdk/-/2dai-cloud-sdk-2.5.2.tgz",
  "fileCount": 10,
  "integrity": "sha512-ClIckweA2vRWYvicGMkXZLAxwF/2v7WqTgX/LOH2NJcWD/9yIKu7+VPVQdDvhUvvwgXrJUgYyTAoub18wbOUPQ==",
  "signatures": [
    {
      "sig": "MEUCIQCtQ1ym5LdlD5zN+ybGw5K9GW+E8aEcgrPau1opCGbSLAIgF1dap+etvOyQjMU734zP5AM6HEfcGHbnL53mgvn/Tsc=",
      "keyid": "SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"
    }
  ],
  "attestations": {
    "url": "https://registry.npmjs.org/-/npm/v1/attestations/2dai-cloud-sdk@2.5.2",
    "provenance": {
      "predicateType": "https://slsa.dev/provenance/v1"
    }
  },
  "unpackedSize": 457112
}

Repository

PENDING
2daicommunity/2dai-cloud-sdk

Shared repository data · GitHub

License detected in repository
Not collected

Metadata history

Meaningful changes
Current collected state Current

Observed since 20 Oct, 19:11 UTC

No superseded metadata states collected. Unchanged observations do not create new revisions.

Record identity

package_version:346b8d26ae4ea3d1339c4db8ec88666d697a6328f04faac3c91f75491a0b1d4e

Other collected versions

3.0.014 Oct2.8.313 Oct2.8.212 Oct2.8.112 Oct2.8.011 Oct2.7.07 OctView all collected versions →