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

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

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

Image to 3D

// From URL (omit version for default v3.0-standard)
const taskId = await lux3d.imgTo3d.create({
img: 'https://example.com/object.jpg',
});

// v3.0 options: face count and optional exports
const taskIdV3 = await lux3d.imgTo3d.create({
img: 'https://example.com/object.jpg',
faceCount: 80_000,
outputFormat: ['zip', 'glb', 'usdz', 'obj_zip'],
});

// 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');

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

Material transfer

const taskId = await lux3d.materialTransfer.create({
img: 'https://example.com/material.jpg',
meshUrl: 'https://example.com/model.glb',
});
const result = await lux3d.tasks.waitFor(taskId);

Version differences

VersionDefaultOutput formats (outputs indices)Notes
v3.0-standardYesFive slots zip / glb / usdz / obj_zip / fbx_zipChoose exports with outputFormat; unrequested slots may be NOT_REQUESTED
v2.0-previewSame five slots as v32.0 architecture
v1.0-proSingle ZIPFull PBR with transparency
G1zip / glb / plybeta; enablePbr / textureSize; multi-view imgs

faceCount (10_000–500_000) applies to v2 / v3 / G1 (v2/v3 default 60_000, G1 default 200_000); v1.0-pro ignores it.

Lux3dTaskResult

FieldTypeDescription
taskIdnumberTask ID
status0 | 1 | 3 | 40 init; 1 running; 3 success; 4 failed
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. 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

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