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.

base URL
https://stream.minuitapi.com/api
⚠️Do not expose the upstream key. If you distribute this API to third parties, add your own customer authentication and rate limits in front of the Minuit gateway.

Endpoints at a glance

MethodPathPurpose
POST/api/livesCreate 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/cloneClone a new voice
GET/api/voicesList 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.

🔞免审增强模式 / No-review mode: send "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.
request
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

FieldTypeRequiredNotes
call_modeStringYesaudio voice-only · video audio + video
moderation 免审StringNostrict enables safety moderation. disabled disables upstream review for adult, age-gated experiences. The Minuit gateway defaults to disabled.
extra_motion 免审组合BooleanNotrue enables richer avatar motion and is the recommended companion setting for no-review mode. The Minuit gateway defaults to true.
avatar.personaStringYesWho the character is. No length limit.
avatar.image_uriStringYesOne 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.nameStringNoDisplay name, ≤20 chars recommended
avatar.voiceStringNoAny supported voice ID or a custom voice returned by the voice cloning API. Default: Tina

Response

200 OK
{
  "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"
  }
}
FieldNotes
live.idThe room id. Every later step needs it — save it.
live.statuswaiting — the room exists, the avatar isn't in yet
live.live_durationMax session length in seconds (auto-disconnect; cap 600)
rtc.tokenMedia-channel ticket, valid ~1 hour. Expired → create a new session.
rtc.user_idYour 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 ↗

web · javascript
// 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

RoleUser id format
You (the user)live-user-{creatorID}-{liveID}
The avatarlive-bot-{creatorID}-{liveID}
Video push streamlive-video-push-{creatorID}-{liveID}
Native apps: channel naming & publish/subscribe map
app modes
### 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}
ℹ️You're in the room now, but the avatar hasn't "powered on" yet — that's the next signal.

WS 3 · Open the control WebSocket

A persistent control line used to say "I'm ready", and later to hang up.

connect
wss://stream.minuitapi.com/api/ws?live_id={live_id}

As soon as the socket opens, send:

conn_init · client → server
{
  "type": 1,
  "live_id": "123456789",
  "seq_id": 1,
  "payload": {
    "conn_init": { "version": 1 }   // version is always 1
  }
}
🔐The Minuit gateway handles WebSocket authentication. Connect directly to the WSS URL above; never append the upstream Minuit key to the URL.

4 · Wait for the avatar

The server answers your conn_init with one of three outcomes:

✅ Ready

conn_init_ack { success: true }

The avatar is live — start talking. Billing starts at this moment.

⏳ NOT_READY

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.

❌ INIT_FAILED

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:

force hangup · server → client
{
  "type": 6,
  "payload": {
    "hangup": {
      "hangup_reason": "xxxx"
    }
  }
}
All hangup_reason values (14)
hangup_reasonTrigger
user_endUser hung up voluntarily
timeoutSession hit its time limit
audit_violationContent-safety system ended the call
credit_insufficientAccount ran out of credits
sip_closedSIP / provider side closed
provider_closedProvider actively closed
sip_reconnect_timeoutSIP dropped and missed the reconnect grace window
client_reconnect_timeoutApp client dropped and missed the reconnect grace window
prepared_sip_disconnectedSIP dropped while the session was still prepared (app never went ready)
owner_taken_overInternal: session owner taken over by another node
owner_lease_lostInternal: owner lease renewal failed
ai_output_closedInternal: AI output channel closed
externalBroadcast close (live:close)
⚠️Besides 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.

hangup · client → server
{
  "type": 5,
  "live_id": "123456789",
  "seq_id": 2,
  "payload": {
    "hangup": { "hangup_reason": "user_end" }
  }
}

Query the session (optional)

request + response
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"
  }
}
FieldNotes
statusended once the session is over
billed_secondsBillable time, counted from the moment the avatar was ready
started_at / ended_atBilling window (Unix timestamps)
credits_costCredits deducted for this session

POST Voice cloning

Turn any mp3/wav sample into a reusable voice, then reference it by name in avatar.voice.

request + response
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" }
FieldTypeRequiredNotes
audio_urlStringYesLink to the audio to clone — mp3 or wav
voiceStringYesYour custom voice name
languageStringNoLanguage hint, e.g. en, zh

List the custom voices created for your account:

request + response
GET https://stream.minuitapi.com/api/voices

{ "voices": [ { "voice": "brand_voice_01" } ] }

Session lifecycle

waitingcreated on_liveboth ends ready · billing on endingclosing endeddone

Errors & troubleshooting

ErrorCauseFix
HTTP 401Missing or invalid API keyCheck the Authorization header
HTTP 403Not the session's creatorUse the same key that created the session
HTTP 404Session doesn't exist or expiredCreate a new session
WS NOT_READYAvatar pipeline still warming upReconnect with exponential backoff
WS LIVE_CONN_INIT_FAILEDInitialization failed for goodCreate a new session

Production checklist

Keep the upstream token in Cloudflare.

Call only the Minuit /api/* gateway from browsers. Add customer authentication and rate limits before offering public access.

RTC is an SDK, not a request.

Aliyun RTC must be integrated and downloaded separately — budget for it in your build.

Expect NOT_READY in video mode.

It's a normal warm-up signal, not an error. Ship retry logic with backoff from day one.

Watch the balance.

Credits are deducted continuously during a call; at zero the session cuts off. Starting a session requires ≥45 credits (a 30-second runway).

Sessions cap at 600 s in beta.

The model itself sustains up to 2 hours — talk to us about longer windows for your use case.

18+
Gate adult experiences.

For NSFW products, enforce 18+ access, consent, jurisdiction rules and your own platform policy before exposing live characters to users.