Skip to main content

TypeScript SDK

Official TypeScript / Node.js SDK for the Aholo Open API.

Installation​

Install only the packages you need:

npm install @manycore/aholo-sdk-asset # file upload
npm install @manycore/aholo-sdk-world@^1.3.0 # world (v1.3.0+ adds insv support)
npm install @manycore/aholo-sdk-lux3d # Lux3D generation

Authentication​

Recommended: set the environment variable; the SDK reads AHOLO_API_KEY:

export AHOLO_API_KEY=your_api_key_here

Or pass it in code:

import { createWorldClient } from '@manycore/aholo-sdk-world';

const world = createWorldClient({ apiKey: 'your_api_key_here', region: 'com' });
Security

Never hardcode API keys in source code, packages, or public repositories.

Region​

ValueDescriptionAPI endpoint
cnChinahttps://api.aholo3d.cn
comGlobalhttps://api.aholo3d.com

Asset upload​

import { createAssetClient } from '@manycore/aholo-sdk-asset';

const asset = createAssetClient({ region: 'com' });

Upload a file​

const result = await asset.uploadFile('./video.mp4');
console.log(result.url); // public URL

Upload a Buffer​

import { readFileSync } from 'node:fs';

const data = readFileSync('./image.jpg');
const result = await asset.uploadBuffer(data, { filename: 'image.jpg' });

Upload with progress​

const result = await asset.uploadFile('./video.mp4', {
onProgress: (uploaded, total) => {
const pct = Math.round((uploaded / total) * 100);
process.stdout.write(`\rUploading: ${pct}%`);
},
});

UploadOptions​

OptionTypeDescription
filenamestringOverride filename (defaults to basename)
onProgress(uploaded: number, total: number) => voidProgress callback (bytes)
partTimeoutMsnumberPer-part timeout (default 120,000 ms)
signalAbortSignalCancellation signal

UploadResult​

FieldTypeDescription
urlstringPublic URL of the uploaded file
md5stringFile MD5

World​

import { createWorldClient, type WorldResourceItem, type WorldResourceType } from '@manycore/aholo-sdk-world';

const world = createWorldClient({ region: 'com' });

Reconstruction uses WorldResourceItem with WorldResourceType (image | video | insv). Generation uses GenerateWorldResourceItem (image only, at most one). type: 'insv' (Insta360 .insv) requires v1.3.0+.

3DGS reconstruction (video / images)​

const { worldId } = await world.reconstructions.create({
name: 'Living room',
resources: [{ url: 'https://cdn.example.com/room.mp4', type: 'video' }], // type: 'video' | 'image' | 'insv'
taskQuality: 'normal', // 'low' | 'normal' | 'high'
scene: 'model', // 'model' | 'space'
useMask: false, // optional: segment uploaded resources when true
});

const detail = await world.waitFor(worldId);
console.log(detail.assets?.splats?.urls?.plyPath); // PLY download URL
Image reconstruction requirement

When using images, you need ≥ 20 image resources (type: 'image' or omit; .jpg/.jpeg/.png/.webp). Standard video: type: 'video' (.mp4/.mov); Insta360 panoramic: type: 'insv' (.insv). URL extension must match type.

Insta360 example:

const { worldId } = await world.reconstructions.create({
name: 'Panoramic living room',
resources: [{ url: 'https://cdn.example.com/room.insv', type: 'insv' }],
taskQuality: 'high',
scene: 'space',
});

3DGS generation (from prompt)​

Generation resources accept images only (type: 'image', at most one; extensions .jpg/.jpeg/.png/.webp). Do not pass video or insv.

const { worldId } = await world.generations.create({
name: 'Forest cabin',
prompt: 'A modern cabin in the forest',
// resources: [{ url: 'https://cdn.example.com/ref.jpg', type: 'image' }], // optional, at most one
});

const detail = await world.waitFor(worldId);

Get world detail​

const detail = await world.retrieve(worldId);
console.log(detail.status);

Task status & polling​

PhaseStatusDescription
In progressPENDINGQueued
In progressPREPROCESSINGPreprocessing
In progressRUNNINGRunning
SuccessSUCCEEDEDSuccess
FailedFAILEDFailed
FailedCANCELEDCanceled
FailedTIMEOUTTimed out
FailedREJECTEDRejected

world.waitFor(worldId) polls until SUCCEEDED and returns WorldDetail. Terminal failures throw PollingFailedError.

List worlds​

const list = await world.list({ pageNum: 1, pageSize: 20 });
list.result?.forEach((w) => console.log(w.worldId, w.status));

WorldDetail fields​

FieldTypeDescription
worldIdstringWorld ID
namestring?Name
statusstringSee task status table above
assets.splats.urls.plyPathstring?PLY download URL
assets.splats.urls.spzPathstring?SPZ download URL
assets.splats.urls.lodMetaPathstring?LOD metadata URL (if generated)
assets.imagery.panoUrlstring?AI panorama URL (Spatial Gen only, after pano subtask succeeds)
assets.semanticsMetadata.upAxis"Y" | "Z"?World up axis (Y = glTF/USD; Z = 3DGS convention)
createTimenumber?Created at (Unix ms)
updateTimenumber?Updated at (Unix ms)

Lux3D​

The following APIs require @manycore/aholo-sdk-lux3d@1.7.0 or later.

import { createLux3dClient } from '@manycore/aholo-sdk-lux3d';

const lux3d = createLux3dClient({ region: 'com' });

Multimodal to image​

Provide at least one of img or prompt.

const taskId = await lux3d.multimodalToImage.create({
prompt: 'A wooden chair product photo, white background',
img: 'https://example.com/object.jpg',
});
const result = await lux3d.tasks.waitFor(taskId);

// From a local file
const taskId2 = await lux3d.multimodalToImage.createFromFile('./object.jpg', {
prompt: 'A wooden chair product photo, white background',
});

Image to four views​

Provide at least one of img or prompt. Prompt-only and image+prompt are both valid.

const taskId = await lux3d.imageToFourView.create({
img: 'https://example.com/object.jpg',
prompt: 'Product four-view, white background',
});
const result = await lux3d.tasks.waitFor(taskId);

const taskId2 = await lux3d.imageToFourView.createFromFile('./object.jpg', {
prompt: 'Product four-view, white background',
});

Image to 3D​

const taskId = await lux3d.imgTo3d.create({
img: 'https://example.com/object.jpg',
version: 'G1', // required: G1 or G1-Turbo
faceCount: 200_000,
outputFormat: ['zip', 'glb', 'ply'],
aiPredictSize: true,
});

// G1 multi-view (local files)
const taskIdG1 = await lux3d.imgTo3d.createFromFiles(
['./view1.png', './view2.png'],
{ version: 'G1', outputFormat: ['glb'], enablePbr: true },
);

// From local file
const taskId2 = await lux3d.imgTo3d.createFromFile('./object.jpg', { version: 'G1-Turbo' });

const result = await lux3d.tasks.waitFor(taskId);
console.log(result.outputs[0]?.content); // default zip download URL

Text to 3D​

const taskId = await lux3d.textTo3d.create({
prompt: 'A wooden chair with carved legs',
version: 'G1',
// style: 'photorealistic', // see styles below
});
const result = await lux3d.tasks.waitFor(taskId);

Text-to-3D styles: photorealistic (default) | cartoon | anime | hand_painted | cyberpunk | fantasy | glass

Multi-format export​

const taskId = await lux3d.multiFormatExport.create({
modelUrl: 'https://example.com/model.glb',
outputFormat: ['usdz', 'obj_zip', 'stl'], // required for GLB input; also supports fbx_zip, 3mf
});
const result = await lux3d.tasks.waitFor(taskId);

List task history​

const page = await lux3d.tasks.list({
page: 1,
pageSize: 20,
status: 3, // optional filter: 0 init, 1 running, 3 success, 4 failed; results may still include 6 canceled
// startTime / endTime: optional Unix timestamps in milliseconds
});
page.items.forEach((task) => console.log(task.taskId, task.status));

Material transfer​

const taskId = await lux3d.materialTransfer.create({
img: 'https://example.com/material.jpg',
meshUrl: 'https://example.com/model.glb',
version: 'v3.0-standard', // required and fixed
aiPredictSize: true,
});
const result = await lux3d.tasks.waitFor(taskId);

Generation parameters​

  • version is required for image/text generation: G1 or G1-Turbo.
  • faceCount ranges from 10,000 to 300,000 and defaults to 200,000; it does not affect PLY.
  • outputFormat supports zip / glb / ply; aiPredictSize defaults to true.
  • For G1-Turbo ZIP/GLB output, enablePbr controls materials. Image generation requires exactly one of img / imgs.

Lux3dTaskResult​

FieldTypeDescription
taskIdnumberTask ID
status0 | 1 | 3 | 4 | 60 init; 1 running; 3 success; 4 failed; 6 canceled
outputsTaskOutput[]Output files (outputs[n].content is download URL, ~2 h TTL after success)

lux3d.tasks.waitFor(taskId) returns when status === 3; throws PollingFailedError on 4 or 6. Poll every 10–15 seconds.


Error handling​

import {
AuthenticationError,
RateLimitError,
BusinessError,
PollingTimeoutError,
PollingFailedError,
} from '@manycore/aholo-sdk-core';

try {
const detail = await world.waitFor(worldId);
} catch (e) {
if (e instanceof AuthenticationError) {
console.error('Invalid or missing API Key');
} else if (e instanceof RateLimitError) {
console.error('Rate limit exceeded');
} else if (e instanceof BusinessError) {
console.error('API error:', e.code, e.message);
} else if (e instanceof PollingTimeoutError) {
console.error('Polling timed out');
} else if (e instanceof PollingFailedError) {
console.error('Task failed:', e.message);
}
}
ErrorDescription
AuthenticationErrorInvalid or missing API Key
RateLimitErrorRate limit exceeded
BusinessErrorAPI business error (includes code)
PollingTimeoutErrorPolling timed out
PollingFailedErrorTask execution failed

More examples​

See GitHub examples:

  • upload-file.mts — upload a local file
  • world-reconstruct.mts — full 3DGS reconstruction flow (.mp4 / .mov / .insv)
  • lux3d-img-to-3d.mts — image to 3D
  • lux3d-multi-format-export.mts — GLB multi-format export

GitHub README is for installation only. If it conflicts with this page, this page wins. Source and runnable examples: GitHub.