API reference / Engine
Engine
Most avatar APIs hand you a pointer to an engine somebody else runs. Ours does not, because the compute is yours: a GPU instance in your account, in a region you pick, that you start, stop and terminate. So this group is wider than a vendor's. It holds the WebRTC signalling itself, the box's own health, and the instance lifecycle.
A vendor's engine call returns a host and a signalling endpoint and then your
client opens WebRTC against that. POST /connect IS the signalling:
you send an SDP offer and it answers with an SDP answer. There is no
intermediate hop and no session id in the response.
| Method | Path | Purpose | Auth |
|---|---|---|---|
| POST | /connect | Open the persistent live stream, SDP in and SDP out | any |
| POST | /offer | One shot: lip sync a named audio file | any |
| GET | /health | Liveness, active avatar, current speaking clip | public |
| GET | /api/stats | Shutdown countdown plus CPU and GPU load | full |
| GET | /auth/gpu/status | Is there a box, and what may this caller launch | Portal, administrator |
| POST | /auth/gpu/start | Start a stopped box | Portal, administrator |
| POST | /auth/gpu/stop | Stop a running box | Portal, administrator |
| POST | /auth/gpu/launch | Create a box | Portal, administrator and owner tier |
| POST | /auth/gpu/terminate | Destroy a box | Portal, administrator |
| GET | /auth/gpu/diagnostics | Why can nobody reach the box | Portal, administrator |
Open the live stream
Opens the one persistent WebRTC session. The avatar idles on a silence clip and replies stream straight into the same connection, so there is no reconnect per message.
Opening a session closes any prior one, along with any one shot /offer
sessions. One box, one live session, by design.
Your WebRTC offer.
The offer's type, normally offer.
Which avatar to stream. Falls back to this caller's saved avatar, then to the box's active one.
An explicit idle clip. When you set this, it wins over the automatic choice.
Sanitized facts about the person, such as their timezone and their name, for this session only. Validated field by field and dropped field by field on failure, so a malformed payload costs a nicety rather than the session. Nothing here is persisted.
{
"sdp": "v=0\r\no=- ...",
"type": "answer",
"avatar": "01-presenter-male",
"silence": "01-presenter-male__silence",
"idle_stand_in": false
}idle_stand_in is true when the idle face had to borrow somebody else's,
because no prepared silent clip existed for the avatar you asked for. It is
worth surfacing: the viewer sees one person idle and a different one speak.
One shot lip sync
Lip syncs an already uploaded audio file into a fresh WebRTC stream. There is
no script and no text to speech in this path: upload the audio first with
POST /api/upload, then name it here.
Filename of an uploaded audio asset. Falls back to the box's default clip. An
unknown name is a 404.
Which avatar to render.
An explicit tone. Without one, and with emotion_responsive on,
the tone is inferred from the audio.
Whether to infer a tone from the audio. Defaults to this caller's setting, then the box's.
Your WebRTC offer.
The offer's type.
{
"sdp": "v=0\r\no=- ...",
"type": "answer",
"avatar": "01-presenter-male__warm",
"emotion": "warm",
"transcript": "Thanks for waiting."
}transcript is what the emotion inference heard, and is empty when the tone was
given explicitly.
Box health
The only route besides the page shell and static assets that takes no credential. Use it as a liveness probe.
{
"status": "ok",
"avatar": "01-presenter-male",
"speaking_face": "01-presenter-male__confident"
}Two facts, not one. avatar is the identity the user picked; speaking_face is
the concrete clip playing right now, which changes every time a reply picks a
tone. Conflating them reports a tone nobody chose.
Runtime stats
Requires a full key, and it is the only GET on the box that does, because
the payload describes the machine rather than the product.
{
"shutdown_minutes": 74,
"cpu_percent": 12.4,
"cpu_cores": 16,
"gpus": [{ "name": "NVIDIA L4", "memory_used_mb": 9214, "utilization": 61 }]
}shutdown_minutes counts down to the box's self termination and is null when
no shutdown is scheduled. It is the field to watch: a box that is about to go is
about to take every live session with it.
GPU lifecycle
These six routes are on the portal plane, not the box. They take a signed in person rather than an API key, and they take a second administrator gate on top of that. An API key will not reach them, and they are callable from a browser only same origin. They are documented here because the instance lifecycle is genuinely part of this API, not because a key holder can drive it.
| Method | Path | What it does |
|---|---|---|
| GET | /auth/gpu/status | Reads the box and what this caller may launch |
| POST | /auth/gpu/start | Starts a stopped box |
| POST | /auth/gpu/stop | Stops a running box |
| POST | /auth/gpu/launch | Creates the thing that bills. 201 |
| POST | /auth/gpu/terminate | Destroys it. The only real off |
| GET | /auth/gpu/diagnostics | Adds EC2 status checks and what the hostname resolves to |
Every verb except the two reads is a POST even where a GET would be natural,
so that no link prefetch and no drive by navigation can spend money.
All six answer the same body shape, so a client polls one thing:
{
"gpu": {
"instance_id": "i-0abc...",
"state": "running",
"instance_type": "g6.xlarge",
"region": "ap-southeast-2",
"availability_zone": "ap-southeast-2b",
"public_ip": "13.x.x.x",
"hostname": "box.avatar.example",
"launched_at": 1769990000,
"running_seconds": 4820,
"max_runtime_seconds": 7200,
"auto_stop_at": 1769997200,
"over_budget": false,
"hourly_cost_usd": 0.8048,
"ephemeral": true,
"can_launch": true,
"bootstrap_configured": true
},
"owner": false,
"regions": [
{
"id": "ap-southeast-2",
"label": "Sydney",
"data_resident": true,
"can_serve": true,
"smoke_test": false,
"types": ["g6.xlarge", "g6.2xlarge"],
"owner_types": []
}
]
}owner and regions sit beside the box rather than inside it, because they
describe the caller rather than the instance: they are how a page learns which
controls to offer. regions with one entry means there is no choice to make.
POST /auth/gpu/launch additionally accepts instance_type and region in the
body. Both are optional, and both are re decided server side against the
verified identity and the deployed environment. A client's copy of the rule
chooses what to show, never what is allowed.
The refusal vocabulary is deliberately granular, so a page can say what happened rather than "something went wrong":
| Status | code |
|---|---|
409 | gpu_already_running, gpu_not_startable, gpu_region_not_allowed |
403 | gpu_type_owner_only |
400 | gpu_type_not_allowed |
503 | gpu_no_capacity, gpu_quota_exceeded, gpu_launch_not_configured |
502 | gpu_unavailable |
Not served in this group
- A managed engine pool. There is no region routing policy, no engine version negotiation and no billing pre check, because there is no pool: there is one box per environment, and its DNS name is re pointed at whatever instance is current.
- A session id in the signalling response.
/connectanswers with an SDP answer and nothing that identifies the session, which is why nothing downstream can be addressed per session.