TypeScript SDK
Official TypeScript / Node.js SDK for the Aholo Open API.
- Requirements: Node.js ≥ 18
- GitHub: manycoretech/aholo-spatial-sdk
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' });
Never hardcode API keys in source code, packages, or public repositories.
Region
| Value | Description | API endpoint |
|---|---|---|
cn | China | https://api.aholo3d.cn |
com | Global | https://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
| Option | Type | Description |
|---|---|---|
filename | string | Override filename (defaults to basename) |
onProgress | (uploaded: number, total: number) => void | Progress callback (bytes) |
partTimeoutMs | number | Per-part timeout (default 120,000 ms) |
signal | AbortSignal | Cancellation signal |
UploadResult
| Field | Type | Description |
|---|---|---|
url | string | Public URL of the uploaded file |
md5 | string | File 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
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
| Phase | Status | Description |
|---|---|---|
| In progress | PENDING | Queued |
| In progress | PREPROCESSING | Preprocessing |
| In progress | RUNNING | Running |
| Success | SUCCEEDED | Success |
| Failed | FAILED | Failed |
| Failed | CANCELED | Canceled |
| Failed | TIMEOUT | Timed out |
| Failed | REJECTED | Rejected |
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
| Field | Type | Description |
|---|---|---|
worldId | string | World ID |
name | string? | Name |
status | string | See task status table above |
assets.splats.urls.plyPath | string? | PLY download URL |
assets.splats.urls.spzPath | string? | SPZ download URL |
assets.splats.urls.lodMetaPath | string? | LOD metadata URL (if generated) |
assets.imagery.panoUrl | string? | AI panorama URL (Spatial Gen only, after pano subtask succeeds) |
assets.semanticsMetadata.upAxis | "Y" | "Z"? | World up axis (Y = glTF/USD; Z = 3DGS convention) |
createTime | number? | Created at (Unix ms) |
updateTime | number? | 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
versionis required for image/text generation:G1orG1-Turbo.faceCountranges from 10,000 to 300,000 and defaults to 200,000; it does not affect PLY.outputFormatsupportszip/glb/ply;aiPredictSizedefaults totrue.- For G1-Turbo ZIP/GLB output,
enablePbrcontrols materials. Image generation requires exactly one ofimg/imgs.
Lux3dTaskResult
| Field | Type | Description |
|---|---|---|
taskId | number | Task ID |
status | 0 | 1 | 3 | 4 | 6 | 0 init; 1 running; 3 success; 4 failed; 6 canceled |
outputs | TaskOutput[] | 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);
}
}
| Error | Description |
|---|---|
AuthenticationError | Invalid or missing API Key |
RateLimitError | Rate limit exceeded |
BusinessError | API business error (includes code) |
PollingTimeoutError | Polling timed out |
PollingFailedError | Task execution failed |
More examples
See GitHub examples:
upload-file.mts— upload a local fileworld-reconstruct.mts— full 3DGS reconstruction flow (.mp4/.mov/.insv)lux3d-img-to-3d.mts— image to 3Dlux3d-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.