Technical documentation
Minuit Live API Reference
Authentication, endpoints, RTC media, WebSocket signalling, session lifecycle and production requirements. All examples use stream.minuitapi.com; the upstream credential remains inside Cloudflare.
Authentication
Call the Minuit gateway through https://stream.minuitapi.com/api. The hosted Cloudflare backend injects the upstream credential, so your browser and app never receive the Minuit vda_... key.
https://stream.minuitapi.com/api
Endpoints at a glance
| Method | Path | Purpose |
|---|---|---|
| POST | /api/lives | Create an avatar session |
| GET | /api/lives/{live_id} | Query session status & billing |
| WS | /api/ws?live_id={live_id} | Control signalling (start / hangup) |
| POST | /api/voices/clone | Clone a new voice |
| GET | /api/voices | List your custom voices |
Host: stream.minuitapi.com · HTTPS and WSS are both served through Cloudflare.
POST 1 · Create a session
Opens the room. Returns the live.id you'll use everywhere, plus RTC credentials for the media channel.
"moderation": "disabled" together with "extra_motion": true. moderation disables the upstream safety review; extra_motion enables richer character motion. Use only for verified 18+ products with your own consent and policy controls.POST https://stream.minuitapi.com/api/lives
Content-Type: application/json
{
"call_mode": "video",
"moderation": "disabled",
"extra_motion": true,
"avatar": {
"persona": "You are a friendly host. Chat with users naturally, in real time.",
"image_uri": "https://stream.minuitapi.com/assets/minuit/leia.webp",
"name": "Leia",
"voice": "Maia"
}
}Request fields
| Field | Type | Required | Notes |
|---|---|---|---|
call_mode | String | Yes | audio voice-only · video audio + video |
moderation 免审 | String | No | strict enables safety moderation. disabled disables upstream review for adult, age-gated experiences. The Minuit gateway defaults to disabled. |
extra_motion 免审组合 | Boolean | No | true enables richer avatar motion and is the recommended companion setting for no-review mode. The Minuit gateway defaults to true. |
avatar.persona | String | Yes | Who the character is. No length limit. |
avatar.image_uri | String | Yes | One single-person image (full or half body, any style). URL or Base64. PNG / JPG / JPEG / WEBP, up to 50 MB. Base64 must decode to <20 MB and include a content-type prefix, e.g. data:image/png;base64,… |
avatar.name | String | No | Display name, ≤20 chars recommended |
avatar.voice | String | No | Any supported voice ID or a custom voice returned by the voice cloning API. Default: Tina |
Response
{
"live": {
"id": "123456789",
"status": "waiting",
"live_duration": 600,
"call_mode": "video"
},
"rtc": {
"app_id": "xxxx",
"channel_id": "live-user-123456789",
"user_id": "live-user-1001-123456789",
"token": "base64-token...",
"token_expire_at": "1750003600"
}
}| Field | Notes |
|---|---|
live.id | The room id. Every later step needs it — save it. |
live.status | waiting — the room exists, the avatar isn't in yet |
live.live_duration | Max session length in seconds (auto-disconnect; cap 600) |
rtc.token | Media-channel ticket, valid ~1 hour. Expired → create a new session. |
rtc.user_id | Your identity inside the RTC channel |
2 · Join the RTC channel
Audio and video are carried by Aliyun real-time channels (ARTC), not HTTP — you must join before you can see or hear the avatar. Download the ARTC SDK ↗
// Subscribe to remote video before joining
aliRtc.subscribeAllRemoteVideoStreams(true);
// Join with the rtc.token and rtc.user_id from step 1
await aliRtc.joinChannel(rtc.token, rtc.user_id);
// Publish your microphone (required — or the avatar can't hear you)
await aliRtc.publishLocalAudioStream(true);
// Publish your camera (video mode only)
await aliRtc.publishLocalVideoStream(true);
// ARTC 7.x reports "subscribed" as state 3
aliRtc.on('videoSubscribeStateChanged', (uid, oldState, newState) => {
if (newState === 3) {
aliRtc.setRemoteViewConfig(
document.querySelector('#remote-video'),
uid,
1
);
}
});Who's who in the channel
| Role | User id format |
|---|---|
| You (the user) | live-user-{creatorID}-{liveID} |
| The avatar | live-bot-{creatorID}-{liveID} |
| Video push stream | live-video-push-{creatorID}-{liveID} |
Native apps: channel naming & publish/subscribe map
### audio mode
join channel: live-audio-{liveID}
my uid: live-user-{creatorID}-{liveID}
publish: mic audio -> live-user-{creatorID}-{liveID}
subscribe: avatar audio -> live-bot-{creatorID}-{liveID}
### video mode
join channel: live-user-{liveID}
my uid: live-user-{creatorID}-{liveID}
publish: mic audio -> live-user-{creatorID}-{liveID}
camera video -> live-user-{creatorID}-{liveID}
subscribe: avatar video -> live-video-push-{creatorID}-{liveID}WS 3 · Open the control WebSocket
A persistent control line used to say "I'm ready", and later to hang up.
wss://stream.minuitapi.com/api/ws?live_id={live_id}As soon as the socket opens, send:
{
"type": 1,
"live_id": "123456789",
"seq_id": 1,
"payload": {
"conn_init": { "version": 1 } // version is always 1
}
}4 · Wait for the avatar
The server answers your conn_init with one of three outcomes:
conn_init_ack { success: true }
The avatar is live — start talking. Billing starts at this moment.
success: false, error_code: "NOT_READY"
Normal in video mode. Close the socket, wait 2–3 s, reconnect (step 3). Use exponential backoff: 2s → 4s → 8s.
error_code: "LIVE_CONN_INIT_FAILED"
Not transient. Go back to step 1 and create a fresh session.
5 · During the call
Keep the heartbeat
The server pings every 5 seconds. Your client must produce some message within 15 seconds or it's treated as dead and force-disconnected. Most WebSocket libraries auto-reply to ping — verify yours does.
Handle forced hangups — required
The server can end the call at any time. This message means you've been disconnected:
{
"type": 6,
"payload": {
"hangup": {
"hangup_reason": "xxxx"
}
}
}All hangup_reason values (14)
| hangup_reason | Trigger |
|---|---|
user_end | User hung up voluntarily |
timeout | Session hit its time limit |
audit_violation | Content-safety system ended the call |
credit_insufficient | Account ran out of credits |
sip_closed | SIP / provider side closed |
provider_closed | Provider actively closed |
sip_reconnect_timeout | SIP dropped and missed the reconnect grace window |
client_reconnect_timeout | App client dropped and missed the reconnect grace window |
prepared_sip_disconnected | SIP dropped while the session was still prepared (app never went ready) |
owner_taken_over | Internal: session owner taken over by another node |
owner_lease_lost | Internal: owner lease renewal failed |
ai_output_closed | Internal: AI output channel closed |
external | Broadcast close (live:close) |
type: 6, also watch the raw WebSocket close/error events as a fallback. Notes: WS messages cap at 64 KB; after a balance or safety hangup, don't auto-reconnect.6 · End the call & read the bill
When you're done, send the hangup over the WebSocket, then close it and call leaveChannel() on the RTC SDK.
{
"type": 5,
"live_id": "123456789",
"seq_id": 2,
"payload": {
"hangup": { "hangup_reason": "user_end" }
}
}Query the session (optional)
GET https://stream.minuitapi.com/api/lives/{live_id}
{
"live": {
"id": "969824102288199680",
"status": "ended",
"call_mode": "video",
"avatar": { "name": "Leia", "voice": "Maia", "persona": "..." },
"billed_seconds": 18,
"credits_cost": 27,
"created_at": "1782895599"
}
}| Field | Notes |
|---|---|
status | ended once the session is over |
billed_seconds | Billable time, counted from the moment the avatar was ready |
started_at / ended_at | Billing window (Unix timestamps) |
credits_cost | Credits deducted for this session |
POST Voice cloning
Turn any mp3/wav sample into a reusable voice, then reference it by name in avatar.voice.
POST https://stream.minuitapi.com/api/voices/clone
Content-Type: application/json
{
"audio_url": "https://stream.minuitapi.com/assets/audio/tina.mp3",
"voice": "brand_voice_01",
"language": "en"
}
{ "voice": "brand_voice_01" }| Field | Type | Required | Notes |
|---|---|---|---|
audio_url | String | Yes | Link to the audio to clone — mp3 or wav |
voice | String | Yes | Your custom voice name |
language | String | No | Language hint, e.g. en, zh |
List the custom voices created for your account:
GET https://stream.minuitapi.com/api/voices
{ "voices": [ { "voice": "brand_voice_01" } ] }Session lifecycle
Errors & troubleshooting
| Error | Cause | Fix |
|---|---|---|
HTTP 401 | Missing or invalid API key | Check the Authorization header |
HTTP 403 | Not the session's creator | Use the same key that created the session |
HTTP 404 | Session doesn't exist or expired | Create a new session |
WS NOT_READY | Avatar pipeline still warming up | Reconnect with exponential backoff |
WS LIVE_CONN_INIT_FAILED | Initialization failed for good | Create a new session |
Production checklist
Call only the Minuit /api/* gateway from browsers. Add customer authentication and rate limits before offering public access.
Aliyun RTC must be integrated and downloaded separately — budget for it in your build.
It's a normal warm-up signal, not an error. Ship retry logic with backoff from day one.
Credits are deducted continuously during a call; at zero the session cuts off. Starting a session requires ≥45 credits (a 30-second runway).
The model itself sustains up to 2 hours — talk to us about longer windows for your use case.
For NSFW products, enforce 18+ access, consent, jurisdiction rules and your own platform policy before exposing live characters to users.