Python SDK
Official Python SDK for the Aholo Open API.
- Requirements: Python ≥ 3.9
- PyPI: manycore-aholo-sdk-*
- GitHub: manycoretech/aholo-spatial-sdk
Installation
Install only the packages you need:
pip install manycore-aholo-sdk-asset # file upload
pip install manycore-aholo-sdk-world # world recon & generation (v1.3.0+ supports insv)
pip install manycore-aholo-sdk-lux3d # Lux3D 3D generation
Authentication
Recommended: set the environment variable; the SDK reads AHOLO_API_KEY automatically:
export AHOLO_API_KEY=your_api_key_here
Or pass the key explicitly:
from manycore.aholo_sdk_world import create_world_client
from manycore.aholo_sdk_core import AholoClientConfig
world = create_world_client(AholoClientConfig(api_key='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
from manycore.aholo_sdk_asset import create_asset_client
asset = create_asset_client(region='com')
Upload a file
result = asset.upload_file('video.mp4')
print(result.url) # public URL
Upload bytes
with open('image.jpg', 'rb') as f:
data = f.read()
result = asset.upload_bytes(data, filename='image.jpg')
Progress callback
def on_progress(uploaded: int, total: int) -> None:
pct = round(uploaded / total * 100)
print(f'\rUpload progress: {pct}%', end='', flush=True)
result = asset.upload_file('video.mp4', on_progress=on_progress)
UploadResult fields
| Field | Type | Description |
|---|---|---|
url | str | Public file URL |
md5 | str | File MD5 |
upload_key | str | None | OUS upload key |
obs_task_id | str | None | OUS task ID |
World
from manycore.aholo_sdk_world import create_world_client
world = create_world_client(region='com')
Reconstruction type supports image, video, and insv (manycore-aholo-sdk-world v1.3.0+). Generation resources accept image only (at most one).
3DGS reconstruction (video / images)
op = world.reconstructions.create(
name='Living room',
resources=[{'url': 'https://cdn.example.com/room.mp4', 'type': 'video'}],
task_quality='normal', # 'low' | 'normal' | 'high'
scene='model', # 'model' | 'space'
use_mask=False, # optional: segment uploaded resources when True
)
detail = world.wait_for(op['worldId'])
print(detail.get('assets', {}).get('splats', {}).get('urls', {}).get('plyPath'))
Image reconstruction requires ≥ 20 image resources (type image; extensions .jpg/.jpeg/.png/.webp). Use type=video for .mp4/.mov; Insta360 panoramic video uses type=insv (.insv). URL extension must match type.
Insta360 example:
op = world.reconstructions.create(
name='Panoramic living room',
resources=[{'url': 'https://cdn.example.com/room.insv', 'type': 'insv'}],
task_quality='high',
scene='space',
)
3DGS generation (from prompt)
Generation resources accept images only (type image, at most one) — not video / insv.
op = 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
)
detail = world.wait_for(op['worldId'])
Get world detail
detail = world.retrieve(world_id)
print(detail.get('status'))
Task status & polling
| Phase | Status | Description |
|---|---|---|
| In progress | PENDING | Queued |
| In progress | PREPROCESSING | Preprocessing |
| In progress | RUNNING | Running |
| Success | SUCCEEDED | Done |
| Failed | FAILED | Failed |
| Failed | CANCELED | Canceled |
| Failed | TIMEOUT | Timed out |
| Failed | REJECTED | Rejected |
world.wait_for(world_id) returns details on SUCCEEDED; failure terminal states raise PollingFailedError.
WorldDetail fields
| Field | Type | Description |
|---|---|---|
worldId | str | World ID |
status | str | Task status |
assets.splats.urls.plyPath | str | None | PLY download URL |
assets.splats.urls.spzPath | str | None | SPZ download URL |
assets.splats.urls.lodMetaPath | str | None | LOD metadata URL |
assets.imagery.panoUrl | str | None | AI-generated panorama URL |
assets.semanticsMetadata.upAxis | str | None | World up axis (Y / Z) |
Lux3D
from manycore.aholo_sdk_lux3d import create_lux3d_client
lux3d = create_lux3d_client(region='com')
Image to 3D
# From URL (omit version for default v3.0-standard)
task_id = lux3d.img_to_3d.create(
img='https://example.com/object.jpg',
)
# v3.0 options: face count and optional exports
task_id_v3 = lux3d.img_to_3d.create(
img='https://example.com/object.jpg',
face_count=80_000,
output_format=['zip', 'glb', 'usdz', 'obj_zip'],
)
# From local file
task_id = lux3d.img_to_3d.create_from_file('./object.jpg')
result = lux3d.tasks.wait_for(task_id)
print(result['outputs'][0]['content']) # default zip download URL
Text to 3D
task_id = lux3d.text_to_3d.create(
prompt='A wooden chair with carved legs',
style='photorealistic', # see styles below
)
result = lux3d.tasks.wait_for(task_id)
Text-to-3D styles:
photorealistic (default) | cartoon | anime | hand_painted | cyberpunk | fantasy | glass
Material transfer
task_id = lux3d.material_transfer.create(
img='https://example.com/material.jpg',
mesh_url='https://example.com/model.glb',
)
result = lux3d.tasks.wait_for(task_id)
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 output_format; 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; enable_pbr / texture_size; 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 fields
| Field | Type | Description |
|---|---|---|
taskId | int | Task ID |
status | int | 0 init; 1 running; 3 success; 4 failed |
outputs | list | Output files (outputs[n]['content'] is download URL, valid ~2 hours after success) |
lux3d.tasks.wait_for(task_id) returns when status == 3; raises PollingFailedError on status == 4. Poll every 10–15 seconds.
Error handling
from manycore.aholo_sdk_core import (
AuthenticationError,
RateLimitError,
BusinessError,
PollingTimeoutError,
PollingFailedError,
)
try:
detail = world.wait_for(world_id)
except AuthenticationError:
print('Invalid or missing API Key')
except RateLimitError:
print('Rate limit exceeded')
except BusinessError as e:
print('Business error:', e.code, e)
except PollingTimeoutError:
print('Polling timed out')
except PollingFailedError as e:
print('Task failed:', e)
| Exception | Description |
|---|---|
AuthenticationError | Invalid or missing API Key |
RateLimitError | Rate limit exceeded |
BusinessError | API business error (includes code) |
PollingTimeoutError | Polling timed out |
PollingFailedError | Task failed |
More examples
See GitHub examples:
| File | Description |
|---|---|
upload_file.py | Upload a local file and print URL |
world_reconstruct.py | Upload video / Insta360 .insv → 3DGS reconstruction → poll to completion |
lux3d_img_to_3d.py | Local image → Lux3D image-to-3D → poll to completion |
After cloning the repo:
export AHOLO_API_KEY=your_api_key_here
# optional: export AHOLO_REGION=com # default cn
pip install -e packages/aholo-sdk-core -e packages/aholo-sdk-asset \
-e packages/aholo-sdk-world -e packages/aholo-sdk-lux3d
python examples/upload_file.py ./photo.jpg
python examples/world_reconstruct.py ./room.mp4 # also .mov, .insv
python examples/lux3d_img_to_3d.py ./chair.png
GitHub README is for installation only. If it conflicts with this page, this page wins. Source and runnable examples: GitHub.