Observed since 20 Oct, 19:10 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
Typed JavaScript/TypeScript client for the 2DAI generation API — generate images & video, upload references, and download results with an API key.
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
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).
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.
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.
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
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.
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.
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.
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 });
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.
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.
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)
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
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.
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.
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.
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"
| Method | Endpoint | Scope | What |
|---|---|---|---|
| GET | /v1/me | read | Account, credit, key scopes |
| POST | /v1/generate/image | generate | Text → image |
| POST | /v1/generate/image-with-refs | generate | Face / character ref, style transfer, smart edit |
| POST | /v1/generate/wallpaper | generate | Wallpaper-resize (priced by dimension) |
| POST | /v1/generate/video | generate | Still → video |
| POST | /v1/uploads | generate | Upload media (multipart) |
| GET | /v1/uploads/check-duplicate | read | md5 pre-flight dedup probe |
| GET | /v1/queue/:queueId | read | Poll a generation |
| POST | /v1/queue/:queueId/cancel | generate | Cancel a pending generation (refunds) |
| GET | /v1/creations | read | List / search your drive (lenses + filters) |
| GET | /v1/creations/:id | read | One creation, full detail |
| GET | /v1/feed | read | The cross-user public feed |
| POST | /v1/creations/:id/move | /trash | /restore | /like | manage | Organise |
| POST | /v1/creations/reorder | /batch | manage | Reorder / bulk actions |
| DELETE | /v1/creations/:id | manage | Permanent delete (trash first) |
| POST | /v1/creations/:id/publish | /unpublish | publish | Public feed |
| GET/POST/PATCH/DELETE | /v1/folders | read / manage | Folders |
| GET/POST/PATCH/DELETE | /v1/folder-groups | read / manage | Sidebar folder groups |
| GET | /v1/stats/overview | /generations | /top | read | Account analytics |
| GET | /v1/wallet*, /v1/account/tier | finance | Read-only wallet data |
| GET | /v1/tiers, /v1/token/price | read | Tier catalogue, cached token quote |
| GET | /cdn/file/:cdnId | read | Download bytes (?s=512 for a preview) |
Full request/response shapes, every error code and the limits are documented in the REST API reference.
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.
MIT
Download counts and popularity will appear here as the collector receives them.
| Version | Published | Direct dependencies | Open version |
|---|---|---|---|
| 3.0.0Registry latest | 14 Oct 2026 | 3 | |
| 2.8.3 | 13 Oct 2026 | 3 | |
| 2.8.2 | 12 Oct 2026 | 3 | |
| 2.8.1 | 12 Oct 2026 | 3 | |
| 2.8.0 | 11 Oct 2026 | 3 | |
| 2.7.0 | 7 Oct 2026 | 3 | |
| 2.6.1 | 6 Oct 2026 | 3 | |
| 2.6.0 | 6 Oct 2026 | 3 | |
| 2.5.9 | 27 Sept 2026 | 3 | |
| 2.5.8 | 27 Sept 2026 | 3 | |
| 2.5.7 | 23 Sept 2026 | 3 | |
| 2.5.6 | 19 Sept 2026 | 3 | |
| 2.5.5 | 10 Sept 2026 | 3 | |
| 2.5.4 | 10 Sept 2026 | 3 | |
| 2.5.3 | 2 Sept 2026 | 3 | |
| 2.5.2 | 2 Sept 2026 | 3 | |
| 2.5.1 | 2 Sept 2026 | 3 | |
| 2.5.0 | 2 Sept 2026 | 3 | |
| 2.4.3 | 23 Aug 2026 | 3 | |
| 2.4.2 | 22 Aug 2026 | 3 |
This describes our collected data; it is not a security assessment.
Shared repository data · GitHub
Observed since 20 Oct, 19:10 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package:b82cb59d4b25d6f628a0715a67495c4321bd174aed38cee555695a5a45205476