Concepts/Templates and roles
Templates and roles
A template is a reusable bundle of roles. A room points at one template, a token names one role in it, and from then on the role decides what that participant may do. Permissions stop being something your application computes.
The shape
Three objects, in a line. A template holds roles and settings. A room references a template. A token references a role.
templateis created once per kind of call you run: a consultation, a class, a webinar.roomtakestemplate_idat creation. Leave it out and the project's default template is used, if it has one.tokentakesrole. The role must exist in the room's template or the request fails with404 role_not_found.
Every permission defaults to false. A role you configure with nothing but a publish list can publish and subscribe, and nothing else. There is no implicit host.
Create a template
Roles arrive as a map keyed by name. A name must match [a-zA-Z0-9_-]{1,64}.
curl -X POST https://api.usevelo.xyz/v1/templates \
-H "Authorization: Bearer $VELO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "clinic",
"roles": {
"practitioner": {
"publish": { "allowed": ["audio", "video", "screen"] },
"subscribe": { "to_roles": ["practitioner", "patient"] },
"permissions": {
"mute_others": true,
"remove_others": true,
"change_role": true
},
"priority": 1
},
"patient": {
"publish": { "allowed": ["audio", "video"] },
"permissions": { "send_data": true },
"max_peer_count": 1
}
}
}'The response returns the template with every role normalised: defaults filled in, simulcast layers resolved, and each role echoed back exactly as the media path will read it. Point a room at it.
curl -X POST https://api.usevelo.xyz/v1/rooms \
-H "Authorization: Bearer $VELO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "consultation-42", "template_id": "tpl_0123456789ab"}'The same thing through the SDK, where role fields are camelCase.
import { VeloClient } from "@usevelo/client";
const velo = new VeloClient({
baseUrl: "https://api.usevelo.xyz",
apiKey: process.env.VELO_API_KEY as string,
});
const template = await velo.createTemplate("clinic", {
roles: {
practitioner: {
publish: { allowed: ["audio", "video", "screen"] },
subscribe: { toRoles: ["practitioner", "patient"] },
permissions: { removeOthers: true, muteOthers: true, changeRole: true },
priority: 1,
},
patient: {
publish: { allowed: ["audio", "video"] },
permissions: { sendData: true },
maxPeerCount: 1,
},
},
});
await velo.createRoom("consultation-42", { templateId: template.id });What a role fixes
| Field | Type | Required | Description |
|---|---|---|---|
| publish.allowed | string[] | No | Any of audio, video, screen. This is the only field that decides whether the token can publish at all, and which sources it may open. |
| publish.audio | object | No | bitrate and codec. Advisory hints applied by the SDK when publishing. |
| publish.video | object | No | width, height, bitrate, framerate, codec. Defaults to 1280x720 at 30fps. |
| publish.screen | object | No | Separate profile for a screen share. Defaults to 1920x1080 at 10fps. |
| publish.simulcast.layers | object[] | No | Up to three layers keyed f, h, q, each with a scale down factor, bitrate cap and framerate cap. |
| subscribe.enabled | boolean | No | Defaults to true. Set false for a role that publishes and receives nothing back. |
| subscribe.to_roles | string[] | No | Which roles this one receives media from. Defaults to every role in the template, including itself. |
| subscribe.max_bitrate | integer | No | Ceiling on what this role pulls down, in kbps. |
| subscribe.degradation | object | No | packet_loss_threshold, degrade_grace_seconds and recover_grace_seconds: when to drop quality on a bad link and how long to wait before climbing back. |
| permissions | object | No | end_room, remove_others, mute_others, unmute_others, change_role, start_recording, stop_recording, send_data, update_own_metadata. All false by default. start_recording grants the role permission to ask; whether the project may record at all is a plan question, and Free does not include recording or live streaming. |
| priority | integer | No | 1 to 5, lower is more important. Defaults to 3. |
| max_peer_count | integer | No | How many participants may hold this role at once. 0 is unlimited and -1 means nobody. |
| hidden | boolean | No | The participant is present but not listed to others. Useful for an observer. |
Resolutions, framerates and subscribe bitrates are advisory: the SDK applies them when publishing and subscribing. The media server enforces the grants carried by the token, which come from publish.allowed, subscribe.enabled, permissions.send_data, permissions.update_own_metadata and hidden. It also measures each participant's total published bitrate against the role ceiling, the sum of the allowed sources with simulcast layers counted individually, and reports sustained overage as a participant.publish_limit_exceeded webhook.
The subscribe graph
subscribe.to_roles is what turns a set of roles into a shape. A webinar is a speaker role that subscribes to speakers, and an attendee role that subscribes to speakers only. Nobody has to filter tracks client side, because attendees never receive them.
- Omit
to_rolesand it is filled in with every role in the template, including this one. - Set
subscribe.enabledto false and the role receives nothing at all. - Every name in
to_rolesmust be a role that exists in the same template, or the request is rejected with400 invalid_rolenaming the offending index. - The graph does not have to be symmetric. Speakers can receive attendees without attendees receiving each other.
Priority and capacity
max_peer_count is enforced at mint time. Asking for a role that is already full fails with 409 role_capacity_reached, and the message says how many the role allows and how many are already in the room. A role set to -1 rejects everyone, which is how you retire a role without deleting it.
priority runs 1 to 5, lower being more important, and rides along on the publish profile so a client can decide what to keep when bandwidth runs short.
Editing a template
PATCH /v1/templates/{id} is surgical: roles named in the body are created or replaced, roles left out are untouched. PUT /v1/templates/{id}/roles/{name} replaces one role whole, so every field you omit falls back to its default rather than to the value it had before. Read the role, edit it, send it back.
A participant can also be moved between roles mid-call. Grants and the velo.role attribute apply immediately; advisory publish hints only take effect when that participant republishes. Every change is written to an append-only audit trail.
curl -X POST \
https://api.usevelo.xyz/v1/rooms/consultation-42/participants/patient-1187/role \
-H "Authorization: Bearer $VELO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"role": "practitioner"}'The endpoints that read and write all of this are listed in the REST API reference.
Was this page useful?