Languages: English | 简体中文 | 繁體中文 | Français | Español | Русский | 日本語
@deckflow/decktools-sdk
TypeScript SDK for DeckTools/Deckflow task APIs in Node.js and browsers.
Install
pnpm add @deckflow/decktools-sdk
In this monorepo:
pnpm --filter @deckflow/decktools-sdk build
Create a Client
Use the root entry point in Node.js. Browser applications should use the dedicated
@deckflow/decktools-sdk/browser entry point documented below.
import { createDeck } from '@deckflow/decktools-sdk';
const deck = createDeck({
root: 'https://app.deckflow.com/v1',
token: 'user-token',
apiKey: 'api-key',
spaceId: 'space-id',
});
Options:
root?: string - API root address. Defaults to https://app.deckflow.com/v1.
token?: string - sent as X-Auth-Token.
apiKey?: string - sent as Authorization: Bearer {apiKey}.
spaceId?: string - default space id for task and file calls.
authUuid?: string - explicit client UUID (UUID v4) sent as X-Auth-UUID. Skips automatic persistence.
authUuidStorage?: { get(), set(value) } - custom storage for client UUID (SSR, tests, embedded apps).
onUnauthorized?: () => Promise<{ token: string; spaceId?: string } | string> - called once after a 401, then the request is retried.
onPaymentRequired?: () => Promise<void> - called once after a 402, then the request is retried.
allowGuestFallback?: boolean - allow expired credentials to be cleared and retried as guest; defaults to true in Node and false in /browser.
retryMutations?: boolean - retry POSTs after transient network/server failures; defaults to true in Node and false in /browser.
token, apiKey, and spaceId are all optional. When spaceId is omitted the SDK resolves it from GET /user, an endpoint that only requires X-Auth-UUID. This means token and apiKey can both be empty: the SDK runs in guest mode, the server identifies the guest by X-Auth-UUID and enforces usage limits and rate quotas. This is useful for try-before-login experiences.
const deck = createDeck({ authUuid: 'f47ac10b-58cc-4372-a567-0e02b2c3d479' });
await deck.generation({ params: { inputText: 'Write a launch plan', pageCount: 8 } });
Every DeckTools API request automatically includes X-Auth-UUID, a stable UUID v4 used to track the client across sessions.
- Browser: persisted in
localStorage under df_uuid.
- Node.js: persisted in
~/.deckflow/auth-uuid (override the directory with DECKFLOW_CONFIG_DIR).
- Explicit override: pass
authUuid or set DECKTOOLS_AUTH_UUID (Node only) for fixed IDs in CI, containers, or multi-tenant servers.
const uuid = await deck.getAuthUuid();
console.log('Client UUID:', uuid);
Browser entry point
The browser entry point shares the task/upload/parse implementation but excludes
Node file and UUID storage code. It is safe to import during SSR. Local paths are
rejected; use File, named Blob, or named binary data instead.
import { createDeck } from '@deckflow/decktools-sdk/browser';
const deck = createDeck({ root: 'https://api.example.com/v1', token: userAccessToken });
const controller = new AbortController();
const parsed = await deck.parse({ file, name: file.name }, {
signal: controller.signal,
onTask: (task) => console.log('Created:', task.id),
upload: { onProgress: (fraction) => console.log('Uploaded:', fraction) },
wait: { onProgress: (task) => console.log(task.status) },
});
const view = await deck.convert({ irKey: parsed.irKey }, { signal: controller.signal });
console.log(view.markdown);
Both facades accept signal and onTask. Upload APIs, task creation, task reads,
downloads, waits and subscriptions also accept signal. Aborting stops HTTP,
uploads, SSE and polling/retry delays; it does not cancel or delete a cloud task
that has already been created. Keep the onTask id to resume inspection later.
An interrupted submission may already exist remotely, so do not blindly submit it again.
Browser authentication fails closed: a 401 may refresh credentials once with
onUnauthorized, but a missing/empty/failed refresh or another 401 raises an error
without switching to guest. Starting without credentials remains an explicit
guest-mode option. Browser POSTs are not automatically retried after ambiguous
transport failures. Do not put long-lived server API keys in browser code; use a
user-scoped token or a backend proxy. API, SSE and storage endpoints must allow
your origin through CORS, including exposing ETag for multipart uploads.
Upload progress is coarse-grained: single/inline uploads report 1 after the
request succeeds; multipart uploads report completed parts up to 0.95 and 1
after completion. No artificial byte-level progress is emitted.
Create Tasks With Files
Pass user-selected files directly to task methods. The SDK uploads them internally and sends the resulting file ids to the task API:
const task = await deck.convertPptToPdf({
files: ['./slides.pptx'],
upload: {
onProgress: (p) => console.log(`${Math.round(p * 100)}%`),
},
});
files supports:
- Node.js file path:
'./a.pptx'
- Node.js/browser binary data:
Uint8Array or ArrayBuffer
- Browser
Blob/File
For browser file pickers, pass the selected File object:
await deck.convertPptToPdf({
files: [file],
});
For binary data without a file name, include per-file upload options:
await deck.imageOcr({
files: [{ input: bytes, name: 'image.png' }],
params: { language: 'en' },
});
For compatibility with existing integrations, fileIds is still accepted and can be combined with files.
Generic Task API
const task = await deck.tasks.create({
type: 'convertor.ppt2pdf',
files: ['./slides.pptx'],
name: 'slides',
params: {},
});
await deck.tasks.list({ type: 'convertor.ppt2pdf', startIndex: 0, maxResults: 50 });
await deck.tasks.get(task.id);
await deck.tasks.wait(task.id, { timeout: 300, useEventStream: true });
await deck.tasks.down<'convertor.ppt2pdf'>(task.id);
await deck.tasks.delete(task.id);
const cancel = await deck.tasks.subscribe(task.id, {
onUpdate: (next) => console.log(next.status),
onError: console.error,
});
cancel();
Task detail responses are for status/progress metadata. Task results should be read through deck.tasks.down(...) or the backend-name alias deck.ttask.down(...).
const result = await deck.ttask.down<'convertor.ppt2pdf'>(task.id);
const generationDownload = await deck.ttask.down<'generation'>(task.id, { type: 'pptx' });
console.log(generationDownload.downloadUrl);
Result Types
The SDK exports concrete result types for every task type through DeckTaskTypeResult.
Most file-producing tasks return tuple-shaped file results because that is the backend contract:
type FileResult = [
path: string,
bytes: number,
hash: string,
];
type ConvertFileResult = [
path: string,
bytes: number,
hash: string,
bounds?: { w?: number; h?: number; total?: number } | null,
];
path is the storage key or relative path in raw backend data. When the task detail API expands downloadable results, it may already be a signed/access URL.
Examples:
deck.convertPptToPdf(...) returns ConvertFileResult[].
deck.convertHtmlToPptx(...) returns { target: FileResult; usedFonts: string[] }.
deck.pptxSplit(...) returns { ppt, sections, slides } with typed slide file metadata.
deck.pptxGetFontInfo(...) returns { fonts, embeddedFont, subsetFont }.
deck.pptxGetTextShapes(...) returns typed page/shape/text/image metadata.
Typed Task Helpers
Every helper accepts { spaceId?, files?, fileIds?, name?, params?, upload? }, sets the backend task type, uploads files when needed, and returns a typed DeckTask.
File and Image
await deck.fileCompress({ files: ['./document.pdf'] });
await deck.imageOcr({ files: [file], params: { language: 'en' } });
await deck.imageConvertWebp({ files: [file] });
await deck.imageResize({ files: [file], params: { maxWidth: 1024 } });
PPTX
await deck.pptxSplit({ files: ['./slides.pptx'], params: { indexes: [0, 1] } });
await deck.pptxJoin({ files: ['./part1.pptx', './part2.pptx'], name: 'merged' });
await deck.pptxGetFontInfo({ files: ['./slides.pptx'] });
await deck.pptxGetTextShapes({
files: ['./slides.pptx'],
params: { includeNotes: true, ignoreEmptyText: true },
});
await deck.pptxEmbedFonts({
files: ['./slides.pptx'],
params: { usedFonts: ['Arial'] },
});
Converters
await deck.convertPptToImage({
files: ['./slides.pptx'],
params: { resolution: 1920, format: 'jpg' },
});
await deck.convertPptToPptx({ files: ['./slides.ppt'] });
await deck.convertPptToPdf({ files: ['./slides.pptx'] });
await deck.convertDocToPdf({ files: ['./handbook.docx'] });
await deck.convertPptToVideo({ files: ['./slides.pptx'] });
await deck.convertPdfToImage({ files: ['./document.pdf'] });
await deck.convertKeynoteToImage({ files: ['./deck.key'] });
await deck.convertKeynoteToHtml({ files: ['./deck.key'] });
await deck.convertKeynoteToPdf({ files: ['./deck.key'] });
await deck.convertHtmlToPng({
files: [{ input: htmlBytes, name: 'page.html' }],
params: { width: 1280, height: 720, fullPage: true },
});
await deck.convertMarkdownToPng({
files: [{ input: markdownBytes, name: 'page.md' }],
params: { theme: 'dark', pageWidth: 960 },
});
await deck.convertHtmlToPptx({
files: [{ input: htmlBytes, name: 'deck.html' }],
params: { width: 1280, height: 720, needEmbedFonts: false },
});
await deck.convertHtmlToPptx({
files: ['./page1.html', './page2.html'],
params: { width: 1280, height: 720 },
});
Ordered multi-source files are meaningful for task types that map the whole file
array into backend parameters, including pptx.join, convertor.html2pptx,
html.buildPlayer, and generation. Most other task types read one source file;
pass one file per task for those.
HTML Player, Generation, Translation, Revamp
await deck.htmlBuildPlayer({
params: {
contents: [{ key: 'pages/1.html' }],
pageWidth: 1280,
pageHeight: 720,
title: 'Deck',
description: 'Deck player',
brandMarkPosition: 'none',
},
});
await deck.generation({
files: [referenceFile],
params: {
inputText: 'Write a product launch plan',
enableSearch: true,
pageCount: 8,
},
});
await deck.translation({
files: [file],
params: {
from: 'zh',
to: 'en',
model: 'Standard',
useGlossary: false,
imageTranslate: false,
},
});
await deck.revamp({
files: [file],
params: { lang: 'zh' },
});
Parse Documents
Parsing and view generation are two separate primitives:
deck.parse() turns a document into an IR — a structured representation
you can hold on to. It never returns Markdown.
deck.convert() turns a stored IR into a view, by reference. It never
re-parses the source.
That split is the point: parse once, convert as many times as you like. The IR
stays available for 7 days, and converting costs no upload and no re-parse.
const parsed = await deck.parse('./slides.pptx');
parsed.ir;
parsed.irKey;
const { markdown } = await deck.convert({ irKey: parsed.irKey });
convert() also accepts the parse task id, which is handy when you stored the
task rather than the key:
await deck.convert({ taskId: parsed.taskId }, { to: 'markdown' });
ir is the response body passed through verbatim, so type it with the task
type's result:
import type { PdfParseResult, PptxParseResult } from '@deckflow/decktools-sdk';
const report = await deck.parse<PdfParseResult>('./report.pdf');
report.ir.document.elements;
Parse params ride on the parse options and are only sent to the task types that
accept them — password, parseProfile, includeImages for .pdf,
stayImageAreaRate for .key:
await deck.parse('./report.pdf', { parseProfile: 'quality', password: 'pw' });
View params belong to convert(), not to parsing — markdownPages for
.pptx / .key, markdownMeta (per-element provenance comments) for .pdf:
await deck.convert({ irKey }, { markdownPages: true });
await deck.convert({ irKey }, { markdownMeta: true });
Already-uploaded files take { fileId, name }; links take { url, mode }:
await deck.parse({ fileId: 'uploaded-file-id', name: 'slides.pptx' });
await deck.parse({ url: 'https://example.com/article', mode: 'runtime' });
Supported extensions are .pdf, .pptx, .docx, and .key. The low-level
helpers (deck.pdfParse → pdf.pdfParse, deck.pptxParse, deck.docxParse,
deck.keynoteParse, deck.htmlGetByURL, deck.convertIr → parse.convert)
take the backend params directly.
Images in a converted view are signed URLs that expire. convert() returns an
images[] manifest — the same shape for every format — mapping each URL to its
persistent key and a suggested on-disk path, so a client that wants to keep the
Markdown can download them and rewrite the links.
Conversion degrades rather than fails by default: when rendering breaks,
markdownError explains why and markdown is empty. Pass
markdownStrict: true to make the task fail instead.
This needs a backend running @deckflow/platform-slave 0.22.0 or newer. Against
older servers parse() throws instead of returning a result with no irKey,
which would be a result nothing could convert.
Browser and Node.js Notes
- Task helpers accept files directly and upload them before task creation.
- Node.js uploads can read a path and calculate MD5.
- Browser uploads can use
Blob/File; the SDK reads the file name and calculates MD5.
- Server-Sent Event subscription works in Node.js and modern browsers; polling remains available with
deck.tasks.wait(taskId, { useEventStream: false }).