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
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
| Version | Default | Output formats (outputs indices) | Notes |
|---|---|---|---|
v3.0-standard | Yes | Five slots zip / glb / usdz / obj_zip / fbx_zip | Choose exports with outputFormat; unrequested slots may be NOT_REQUESTED |
v2.0-preview | Same five slots as v3 | 2.0 architecture | |
v1.0-pro | Single ZIP | Full PBR with transparency | |
G1 | zip / glb / ply | beta; 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
| Field | Type | Description |
|---|---|---|
taskId | number | Task ID |
status | 0 | 1 | 3 | 4 | 0 init; 1 running; 3 success; 4 failed |
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. 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 3D
GitHub README is for installation only. If it conflicts with this page, this page wins. Source and runnable examples: GitHub.