Skip to content

API Reference

2DAITeam edited this page Jul 30, 2026 · 19 revisions

API Reference

new Client(options)

interface ClientOptions {
  apiKey: string;               // required — '2dai_sk_…'
  baseUrl?: string;             // default 'https://dapp.2dai.io:444'
  fetch?: typeof fetch;
  timeoutMs?: number;           // default 60000
  maxRetries?: number;          // default 2
  integration?: 'sdk' | 'mcp';  // default 'sdk'
  integrationVersion?: string;  // semver, e.g. '1.4.0'
}

integration is a closed set, not a free-form User-Agent: the server derives each creation's source from the client token, and source is what puts a row in the dashboard's SDK / MCP collections and picks its origin pill. Only the official 2DAI MCP server sends 'mcp' — your own app, a script or a backend job leaves this alone and lands as sdk. integrationVersion rides along for diagnostics and must look like 1.2.3 (a prerelease or build suffix is fine); a malformed value is replaced by 0.0.0 rather than omitted — the token has to match name/x.y.z for the server to recognise the integration at all, so an honest placeholder beats losing the attribution.

  • client.baseUrl: string
  • client.me(signal?): Promise<Account>
  • client.download(ref, opts?: DownloadOptions) — shortcut for client.cdn.download

client.generate

generate.image(params: ImageParams, opts?: SubmitOptions): Promise<GenerationResult>       // { wait:true } default
generate.image(params: ImageParams, opts: { wait:false } & SubmitOptions): Promise<QueueTicket>
generate.imageWithRefs(params: RefParams, opts?): Promise<GenerationResult | QueueTicket>
generate.video(params: VideoParams, opts?): Promise<GenerationResult | QueueTicket>

SubmitOptions: { wait? (default true), intervalMs? (default 2000), timeoutMs? (default 300000), signal? } — same defaults for WaitOptions.

interface RefParams {
  tool: 'face-ref' | 'character-ref' | 'style-transfer';
  refCreationIds: string[];      // ≤6 identity refs (face/character) · ≤3 style sources
  prompt?; quality?; aspectRatio?; width?; height?; allowNSFW?;
  extractionDirective?;          // style-transfer only, default 'style'
  clientToken?;
}
interface VideoParams {
  prompt: string; inputCreationId: string;
  duration?;                     // seconds, tier-gated
  quality?; style?; frameInterpolation?; allowNSFW?; clientToken?;
}

client.queue

queue.get(queueId: string, signal?): Promise<QueueState>
queue.waitFor(queueId: string, opts?: WaitOptions): Promise<QueueState>  // resolves on any terminal status

client.creations

creations.list(opts?: ListOptions): Promise<CreationPage>
creations.get(ref: CreationRef, signal?): Promise<Creation>
creations.move(ref, target: string | null, signal?): Promise<{ creationId, folderId }>  // 'root'|null = detach
creations.trash(ref, signal?): Promise<{ creationId, trashed }>
creations.restore(ref, signal?): Promise<{ creationId, trashed }>
creations.delete(ref, signal?): Promise<{ creationId, deleted }>          // must be trashed first
creations.publish(ref, signal?): Promise<{ creationId, isPublicShared }>
creations.unpublish(ref, signal?): Promise<{ creationId, isPublicShared }>

ListOptions: { limit?, beforeDate?, folderId?, trashed?, signal? } (folderId: 'root' = ungrouped root).

client.folders

folders.list(arg?: AbortSignal | FolderListOptions): Promise<FolderPage>
folders.create(input: { title, description? }, signal?): Promise<Folder>
folders.update(folderId, patch: { title?, description? }, signal?): Promise<Folder>
folders.delete(folderId, opts?: { trashContents?, signal? }): Promise<{ folderId, deleted }>

interface FolderListOptions { limit?; beforeDate?; sort?: FolderSort; signal? }
type FolderSort = 'index' | 'date-desc' | 'date-asc' | 'name-asc' | 'name-desc' | 'update-desc'

list returns a page (newest first by default, limit 1..100 default 20) — paginate with nextBeforeDate. The cursor is only emitted for the default and date-desc orderings: beforeDate filters on the creation date, so any other sort is honoured but comes back without nextBeforeDate — raise limit instead. The bare-AbortSignal call form (folders.list(signal)) still works.

client.uploads

uploads.image(input: UploadInput): Promise<Creation>
// UploadInput: { path? } | { data: Uint8Array|ArrayBuffer|Blob } | { base64 }, + filename?, contentType?, signal?

client.cdn

cdn.url(ref: CdnRef, transform?: CdnTransform): string
cdn.fetch(ref: CdnRef, arg?: CdnFetchArg): Promise<CdnAsset>   // { bytes, contentType, ext }
cdn.download(ref, opts?: DownloadOptions): Promise<string | CdnAsset>
cdn.toBlob(ref, arg?: CdnFetchArg): Promise<Blob>
cdn.toDataUrl(ref, arg?: CdnFetchArg): Promise<string>

interface CdnTransform {
  width?: number;      // → ?w — target width in px
  height?: number;     // → ?h — target height in px
  maxSide?: number;    // → ?s — longest edge in px, aspect ratio preserved
}
interface CdnFetchOptions extends CdnTransform { signal?: AbortSignal }
interface DownloadOptions extends CdnTransform { savePath?: string; signal?: AbortSignal }
type CdnFetchArg = AbortSignal | CdnFetchOptions

The resize happens on the CDN, before the bytes are sent — nothing is decoded client-side, so a preview costs a fraction of the transfer. maxSide on its own scales the longest edge and keeps the aspect ratio; width / height target those axes specifically and may crop. Non-finite or non-positive values throw ApiError('INVALID_TRANSFORM') locally, before any request goes out; valid ones are rounded to whole pixels. fetch / toBlob / toDataUrl still accept a bare AbortSignal as their second argument, so pre-transform code keeps working; pass { …transform, signal } to combine the two. download composes the same way — savePath plus a transform writes a resized file.

Core types

interface Account { userId; username?; creditUsd; tier; key: KeyContext }
interface KeyContext { keyId; label; scopes: Scope[]; spendLimitUsd: number|null; spentUsd }
type Scope = 'read' | 'generate' | 'manage' | 'publish'

interface QueueTicket { queueId; status: QueueStatus; costUsd? }   // costUsd present when a charge applied
interface ApiErrorBody { error?; message?; [key: string]: unknown } // raw parsed error payload (err.details)

interface GenerationResult extends Creation { queueId; costUsd? }
interface Creation {
  creationId; prompt?; toolKind?; style?; quality?; aiModel?;
  cdnId?; downloadUrl?; width?; height?; isUploaded?; source?; nsfwRate?; creationDate?; raw?
  // source: 'web' | 'sdk' | 'mcp' | 'api' — creation origin (SDK ≥ 2.0.1)
}
interface QueueState { queueId; status; costUsd?; creationId?; cdnId?; downloadUrl?; error?; createdAt?; completedAt? }
type QueueStatus = 'pending'|'pendingCharge'|'processing'|'inProgress'|'completed'|'failed'|'cancelled'|'dismissed'|'timeout'|'expired'

interface Folder { folderId; title?; description?; icon?; kind?; isPublicShared?; isShared?; posterCreationId?; createdAt?; updatedAt?; raw? }
interface CreationPage { creations: Creation[]; nextBeforeDate? }
interface FolderPage   { folders: Folder[]; nextBeforeDate? }
type CdnRef = string | { cdnId?; outputCdnId?; downloadUrl? }
type CreationRef = string | { creationId? }

type AspectRatio = '1:1'|'16:9'|'9:16'|'4:3'|'3:4'|'3:2'|'2:3'

See Error Handling for the full error class table. For the underlying HTTP endpoints (any language), see REST API.

Clone this wiki locally