AIMoCap Docs
API Reference
Request and response fields for AIMoCap async mocap jobs, plus a Python example.
Async workflow
1. Create a job
POST /api/v1/mocap/jobs
Request body:
{
"title": "tennis-02",
"sourceFilename": "tennis_02.mp4",
"sourceDurationSec": 18.4,
"trimStartSec": 1.2,
"trimEndSec": 13.8,
"exportFps": 30,
"mocapType": "full_body",
"targetIds": ["default", "unitree_g1"]
}
Field reference:
| Field | Type | Required | Notes |
|---|---|---|---|
title |
string | No | Optional job title. |
sourceFilename |
string | Yes | Original video filename. |
sourceDurationSec |
number | No | Video duration in seconds; optional. |
trimStartSec |
number | No | Trim start time in seconds. Defaults to 0. |
trimEndSec |
number | No | Trim end time in seconds. Defaults to the end of the uploaded video. |
exportFps |
number | No | 24, 30, 60, or 120. Defaults to 30 when omitted. |
mocapType |
string | No | full_body or upper_body. Defaults to full_body when omitted. |
targetIds |
string[] | Yes | One job can request one or more outputs. Currently supports default and unitree_g1. |
Common create-time errors:
export_fps_invalidmocap_type_invalidapi_target_ids_requiredapi_target_unsupportedapi_source_duration_invalidapi_duration_limit_exceededapi_file_size_limit_exceededapi_trim_range_invalidapi_concurrency_limit_exceededapi_plan_inactiveinsufficient_api_v_credits
Response:
{
"jobId": "mocap_xxx",
"status": "draft",
"mocapType": "full_body",
"uploadUrl": "https://...",
"sourceKey": "..."
}
2. Upload the source file
Upload the source video to uploadUrl with PUT.
3. Complete upload
POST /api/v1/mocap/jobs/{jobId}/complete-upload
After upload, call this endpoint to submit the job for processing. AIMoCap checks the uploaded file, requested trim range, account limits, and available API v-credit before the mocap algorithm starts.
If the uploaded file or requested clip is outside your API plan limits, the job fails with a clear error code and no v-credit is consumed.
4. Billing
API jobs use API v-credit, separate from normal web credits.
V-credit is consumed after the source video is accepted for processing. The amount is based on the final processed clip duration in seconds, rounded up. If a system-side processing failure happens after v-credit is consumed, AIMoCap refunds the job v-credit once.
5. Poll job status
GET /api/v1/mocap/jobs/{jobId}
Example response fields:
{
"job": {
"id": "mocap_xxx",
"status": "processing",
"targetIds": ["default", "robot:unitree_g1"],
"exportFps": 30,
"mocapType": "full_body"
},
"events": [],
"result": null
}
6. Read the final result
GET /api/v1/mocap/jobs/{jobId}/result
Common result fields:
| Field | Notes |
|---|---|
jobId |
Job identifier. |
status |
Final job status. |
targetIds |
Requested public output targets, such as default and unitree_g1. |
mocapType |
Capture type used by the job: full_body or upper_body. |
previewVideoUrl |
Preview video URL. |
outputs |
Per-target output array. |
createdAt |
Creation timestamp. |
updatedAt |
Last update timestamp. |
completedAt |
Completion timestamp. |
Each item in outputs uses this shape:
| Field | Notes |
|---|---|
targetId |
default or unitree_g1. |
resultType |
fbx or robot_motion_json. |
status |
Output status. |
fbxUrl |
Present for default. |
motionJsonUrl |
Present for unitree_g1. |
Notes:
- One API job can produce both FBX and Unitree G1 robot motion data.
- Public integrations should read
outputs[]to find each target’s result URL. - Custom avatar targets are planned for a later public API update.
Python example
import time
import requests
BASE_URL = "https://aimocap.net"
API_KEY = "YOUR_AIMOCAP_API_KEY"
VIDEO_PATH = "tennis_02.mp4"
headers = {
"Authorization": f"Bearer {API_KEY}",
}
with open(VIDEO_PATH, "rb") as f:
video_bytes = f.read()
create_payload = {
"title": "tennis-02",
"sourceFilename": "tennis_02.mp4",
"trimStartSec": 1.2,
"trimEndSec": 13.8,
"exportFps": 30,
"mocapType": "full_body",
"targetIds": ["default", "unitree_g1"],
}
create_resp = requests.post(
f"{BASE_URL}/api/v1/mocap/jobs",
json=create_payload,
headers=headers,
)
create_resp.raise_for_status()
create_data = create_resp.json()["data"]
upload_resp = requests.put(
create_data["uploadUrl"],
data=video_bytes,
headers={"Content-Type": "video/mp4"},
)
upload_resp.raise_for_status()
complete_resp = requests.post(
f"{BASE_URL}/api/v1/mocap/jobs/{create_data['jobId']}/complete-upload",
headers=headers,
)
complete_resp.raise_for_status()
while True:
job_resp = requests.get(
f"{BASE_URL}/api/v1/mocap/jobs/{create_data['jobId']}",
headers=headers,
)
job_resp.raise_for_status()
job_data = job_resp.json()["data"]
status = job_data["job"]["status"]
if status in {"completed", "failed", "canceled"}:
break
time.sleep(5)
result_resp = requests.get(
f"{BASE_URL}/api/v1/mocap/jobs/{create_data['jobId']}/result",
headers=headers,
)
result_resp.raise_for_status()
result = result_resp.json()["data"]
print("preview:", result.get("previewVideoUrl"))
for output in result.get("outputs", []):
print(output["targetId"], output["resultType"], output.get("fbxUrl") or output.get("motionJsonUrl"))
