Skip to content

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.

  • template is created once per kind of call you run: a consultation, a class, a webinar.
  • room takes template_idat creation. Leave it out and the project's default template is used, if it has one.
  • token takes role. The role must exist in the room's template or the request fails with 404 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
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
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.

typescriptserver
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

Role fields
FieldTypeRequiredDescription
publish.allowedstring[]NoAny 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.audioobjectNobitrate and codec. Advisory hints applied by the SDK when publishing.
publish.videoobjectNowidth, height, bitrate, framerate, codec. Defaults to 1280x720 at 30fps.
publish.screenobjectNoSeparate profile for a screen share. Defaults to 1920x1080 at 10fps.
publish.simulcast.layersobject[]NoUp to three layers keyed f, h, q, each with a scale down factor, bitrate cap and framerate cap.
subscribe.enabledbooleanNoDefaults to true. Set false for a role that publishes and receives nothing back.
subscribe.to_rolesstring[]NoWhich roles this one receives media from. Defaults to every role in the template, including itself.
subscribe.max_bitrateintegerNoCeiling on what this role pulls down, in kbps.
subscribe.degradationobjectNopacket_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.
permissionsobjectNoend_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.
priorityintegerNo1 to 5, lower is more important. Defaults to 3.
max_peer_countintegerNoHow many participants may hold this role at once. 0 is unlimited and -1 means nobody.
hiddenbooleanNoThe 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_roles and it is filled in with every role in the template, including this one.
  • Set subscribe.enabled to false and the role receives nothing at all.
  • Every name in to_roles must be a role that exists in the same template, or the request is rejected with 400 invalid_role naming 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
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?

Edit this page on GitHub