Quick Start
Authenticate, submit a Rodin task, poll it, and download the result.
Prerequisites
Create an API key in the API dashboard.
Keep credentials out of your code
Never hard-code or commit API keys or other credentials. Load them from environment variables or a secrets manager instead. Example:
export RODIN_API_KEY='replace-with-your-api-key'Every protected request uses this header:
Authorization: Bearer YOUR_RODIN_API_KEY1. Submit a generation
The example below submits one image to Rodin Gen-2.5. Up to five images can be sent by repeating the images form field.
curl --fail-with-body --request POST 'https://api.hyper3d.com/api/v2/rodin' \
--header "Authorization: Bearer ${RODIN_API_KEY}" \
--form 'images=@./input.png' \
--form 'tier=Gen-2.5-Medium' \
--form 'mesh_mode=Raw' \
--form 'quality=medium'
The response contains two identifiers with different purposes:
jobs.subscription_key: send this to/status.- Top-level
uuid: send this astask_uuidto/download.
{
"message": "Submitted.",
"uuid": "123e4567-e89b-12d3-a456-426614174000",
"jobs": {
"uuids": ["223e4567-e89b-12d3-a456-426614174000"],
"subscription_key": "subscription-key-from-generation-response"
},
"consumed": 0.5
}
2. Check status
Wait at least five seconds before the first check. Back off on repeated requests and honor Retry-After after HTTP 429. Stop immediately if any job is Failed; continue only when every job is Done.
curl --fail-with-body --request POST 'https://api.hyper3d.com/api/v2/status' \
--header "Authorization: Bearer ${RODIN_API_KEY}" \
--header 'Content-Type: application/json' \
--data '{"subscription_key":"subscription-key-from-generation-response"}'
3. Download results
Call /download only after every job is complete, using the generation response's top-level uuid.
curl --fail-with-body --request POST 'https://api.hyper3d.com/api/v2/download' \
--header "Authorization: Bearer ${RODIN_API_KEY}" \
--header 'Content-Type: application/json' \
--data '{"task_uuid":"123e4567-e89b-12d3-a456-426614174000"}'
Complete Python workflow
This example starts polling after 5 seconds, increases the delay to at most 30 seconds, enforces a 20-minute deadline, handles HTTP 429, fails on a failed job, and downloads only after all jobs finish.
import os
import time
from email.utils import parsedate_to_datetime
from pathlib import Path
import requests
BASE_URL = "https://api.hyper3d.com/api/v2"
DEADLINE_SECONDS = 20 * 60
def request_with_rate_limit(session, method, url, **kwargs):
while True:
response = session.request(method, url, timeout=60, **kwargs)
if response.status_code != 429:
response.raise_for_status()
return response.json()
retry_after = response.headers.get("Retry-After", "5")
try:
delay = max(1, int(retry_after))
except ValueError:
delay = max(1, int((parsedate_to_datetime(retry_after) - parsedate_to_datetime(response.headers["Date"])).total_seconds()))
time.sleep(delay)
session = requests.Session()
session.headers["Authorization"] = f"Bearer {os.environ['RODIN_API_KEY']}"
with Path("input.png").open("rb") as image_file:
generation = request_with_rate_limit(
session,
"POST",
f"{BASE_URL}/rodin",
files={"images": ("input.png", image_file, "image/png")},
data={"tier": "Gen-2.5-Medium", "mesh_mode": "Raw", "quality": "medium"},
)
subscription_key = generation["jobs"]["subscription_key"]
task_uuid = generation["uuid"]
started_at = time.monotonic()
delay = 5
while True:
if time.monotonic() - started_at >= DEADLINE_SECONDS:
raise TimeoutError("Generation did not finish within 20 minutes")
time.sleep(delay)
status = request_with_rate_limit(
session,
"POST",
f"{BASE_URL}/status",
json={"subscription_key": subscription_key},
)
states = [job["status"] for job in status["jobs"]]
if "Failed" in states:
raise RuntimeError(f"Generation failed: {status}")
if states and all(state == "Done" for state in states):
break
delay = min(delay + 5, 30)
downloads = request_with_rate_limit(
session,
"POST",
f"{BASE_URL}/download",
json={"task_uuid": task_uuid},
)
print(downloads)