Reference/REST API
REST API
Every project endpoint on https://api.usevelo.xyz, grouped by the resource it acts on. This page is generated from the OpenAPI document Velo publishes, so it cannot drift from the API it describes.
Conventions
Requests and responses are JSON. Bodies use snake_case. Timestamps are RFC 3339 in UTC. Creation returns 201, deletion returns 204, everything else returns 200. A request body is capped at 65536 bytes.
List endpoints take limit and offset, default 50, maximum 200, and answer with the collection alongside limit, offset and has_more.
curl -G https://api.usevelo.xyz/v1/rooms \
-H "Authorization: Bearer $VELO_API_KEY" \
-d limit=50 \
-d offset=0Endpoints marked as a moderation call accept two credentials: a project API key, which always acts for the project, or a room token whose role carries the matching permission. Everything else needs the project API key. Unsafe calls accept an Idempotency-Key header; replaying the same key with a different body fails with 409 idempotency_key_reuse.
Every failure, at every status, uses one envelope. code is stable and safe to branch on, message is prose, and field names the request field or the missing permission when the failure is about one of them.
{
"error": {
"code": "room_not_found",
"message": "room not found",
"field": "name"
}
}See Authentication for which credential belongs where.
Call this API from your server, not from a browser. Velo answers cross-origin preflight only for the console itself, and does not accept an Authorization header cross-origin, so a request from your own web page is refused with 403 origin_rejected. This is deliberate: an API key in a browser is a public API key. Browsers get a room token, minted by your server, and use it with a client SDK.
Room names are used once
A room name belongs to one room for the life of the project. Creating a room with a name the project has used before returns 409 room_exists, whether or not that room is still running. Deleting a room ends it; the name is not released. Minting a token against a room that has ended returns 409 room_ended.
So a room name is a one-time identifier, not a stable address. Derive it from the thing that happens once, the consultation or the booking attempt, rather than from the thing that recurs, the customer or the weekly meeting. Store the name you generated and read it back; do not recompute it and hope it matches.
const name = `consult-${appointmentId}-${crypto.randomUUID().slice(0, 8)}`;
const room = await velo.rooms.create({ name, template_id: "tpl_consult" });
await db.appointments.update(appointmentId, { veloRoomName: room.name });If two of your own workers might create the same room at once, make the write that stores the name the thing that decides the winner, and end the room the loser created. Two rooms both count against the plan's concurrent room ceiling until one of them ends.
Rate limits
Velo allows 20 requests a second with a burst of 40. The bucket is the API key, or the client address for calls that carry no key, so one project's traffic cannot slow another's. Going over returns 429 rate_limited with a Retry-After header in seconds. Honour it. Retrying immediately keeps the bucket empty and makes the outage last longer than it needed to.
Exchanging a room code is limited separately and far more tightly, to 1 a second with a burst of 5, per address, because that is the one route open to the public internet without a credential.
Nothing else is metered by request count. What a plan caps is concurrent rooms, people per room and participant minutes a month, and those refuse with 402, not 429.
Errors
Every code Velo can return, by status. code is stable and safe to branch on; message is prose and may be reworded. Treat a code you do not recognise as its status class.
400 Bad Request — the request itself is wrong
- invalid_request
- A field is missing, malformed, or outside its allowed set. field names it.
- invalid_room_name
- A room name must match
[a-zA-Z0-9_-]{1,128}. - invalid_identity
- A participant identity must match
[a-zA-Z0-9_.@-]{1,128}. - invalid_url
- A destination or endpoint URL is not a valid http(s) URL.
- blocked_url
- The URL resolves to a private or link-local address. Velo will not call it.
- invalid_filepath
- A recording filepath must be a relative name inside the project's recording directory.
401 Unauthorized — the credential is missing, malformed or revoked
- unauthorized
- No usable credential. Check the Authorization header and that the key is still live.
403 Forbidden — the credential is real but may not do this
- permission_denied
- A room token whose role lacks the permission this call needs.
fieldnames the missing permission. - origin_rejected
- A browser tried to call the API. See Calling from a server, above.
402 Payment Required — the plan or the wallet will not carry it
- room_limit_reached
- The plan's concurrent room ceiling is already taken. End a room or move up a plan.
- quota_exceeded
- The month's participant minutes are spent, so rooms, tokens and recordings are refused until the period rolls over. Also returned when a project is suspended.
- feature_not_in_plan
- Recording or live streaming on a plan that does not include it, which is what Free is.
fieldisrecordingorstreaming. - insufficient_balance
- The wallet cannot cover the call.
404 Not Found — no such thing, or not yours
- room_not_found
- No room by that name in this project.
- session_not_found
- No session by that id.
- template_not_found
- No template by that id, or the room resolves to no template.
- role_not_found
- No role by that name in this template.
- destination_not_found
- No destination by that name on this template.
- recording_not_found
- No recording by that egress id.
- broadcast_not_found
- No broadcast by that egress id.
- ingress_not_found
- No ingress by that id.
- endpoint_not_found
- No webhook endpoint by that id.
- delivery_not_found
- No webhook delivery by that id.
- code_not_found
- No room code by that value, or it has been revoked.
409 Conflict — the state will not allow it
- room_exists
- This project has used that room name before. Names are used once.
- room_ended
- The room has ended, so no new tokens can be minted for it. Create a new room.
- idempotency_key_reuse
- The same Idempotency-Key arrived with a different body.
- recording_exists
- That egress is already being recorded.
- broadcast_exists
- That egress is already being broadcast.
- ingress_exists
- That ingress is already registered.
- template_exists
- A template with that name already exists.
- template_in_use
- Rooms still reference this template.
- template_is_default
- The project's default template cannot be deleted.
- destination_exists
- A destination with that name already exists on this template.
- destination_in_use
- A recording or broadcast is still running against it. Stop that first.
- role_capacity_reached
- The role is full for this room.
- role_referenced
- Participants still hold this role, so it cannot be removed.
- delivery_not_retryable
- Only a delivery whose status is failed can be retried.
413 Payload Too Large
- payload_too_large
- The request body exceeds 65536 bytes.
429 Too Many Requests
- rate_limited
- Slow down and honour
Retry-After. See Rate limits, above.
500 and 503 Ours, not yours — safe to retry with backoff
- internal_error
- An unhandled fault. Retry; if it persists, the status page will say so.
- database_unavailable
- Velo could not reach its database.
- billing_unavailable
- Velo could not reach billing, so it would not confirm the call is within plan.
- destination_unusable
- The stored credentials for this destination could not be decrypted. Re-save them.
The OpenAPI document
Velo publishes an OpenAPI 3.1.0 description of the whole project API at openapi.json. Every endpoint below is read out of that file at build time, including the paths, the request fields, the response fields and the credential each one takes. Point a generator at it and the client you get back is the API you are actually calling.
curl -O https://usevelo.xyz/openapi.json
npx @redocly/cli lint openapi.jsonThe tables below list the fields of each request and each response one level deep. Where a field's type is a named object, such as Role or PublishProfile, its own fields are in the document under components.schemas.
The document describes 54 operations: 53 that need a project API key, of which 9 are moderation calls that a room token can also make, and 1 open route for exchanging a room code. The collection at Velo API.postman_collection.json carries 60 requests. It covers the features a project key can reach and nothing else: the admin routes, the billing service and the metrics endpoints are deliberately absent, because a project key cannot call them and publishing them would only describe a door you have no reason to knock on.
Rooms
A room is created before anyone joins and holds its name, capacity, metadata and template for the life of the call.
List rooms
GET/v1/rooms
Every room in the project, newest first, including rooms that have ended.
Project API key.
| Field | Type | Required | Description |
|---|---|---|---|
| limit | integer · query | No | Page size, between 1 and 200. Defaults to 50. |
| offset | integer · query | No | How many rows to skip. Defaults to 0. |
Response 200 RoomPage
A page of rooms.
| Field | Type | Required | Description |
|---|---|---|---|
| rooms | Room[] | Yes | The rooms in this page, newest first. |
| limit | integer | Yes | The page size that was applied. |
| offset | integer | Yes | The offset that was applied. |
| has_more | boolean | Yes | True when a further page exists. |
Errors: 400, 401, 429, 500. Every one carries the same error envelope.
Create a room
POST/v1/rooms
Creates the room before anyone joins. The room holds its name, capacity, metadata and template for the life of the call. Without template_id the project's default template is used, if it has one.
Project API key. Accepts an Idempotency-Key header.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
| name | string | Yes | Unique within the project. Must match ^[a-zA-Z0-9_-]{1,128}$. |
| max_participants | integer | No | 0 means the plan limit applies. Defaults to 0. |
| empty_timeout_seconds | integer or null | No | How long the room survives with nobody in it. Defaults to 300. |
| metadata | any | No | Any JSON. Returned unchanged on every read of the room. |
| template_id | string | No | Which template the room resolves roles against. Must match ^tpl_[0-9a-f]{12}$. |
Response 201 Room
The room was created.
| Field | Type | Required | Description |
|---|---|---|---|
| id | uuid | Yes | Velo's internal identifier for the room row. |
| project_id | string | Yes | The project the room belongs to. |
| name | string | Yes | The name you created the room with. |
| namespaced_name | string | Yes | The name the media plane sees, which is the project id, a dot, then the room name. |
| max_participants | integer | Yes | The cap requested at creation. Zero means the plan limit applies. |
| empty_timeout_seconds | integer | Yes | How long the room stays open with nobody in it. Defaults to 300. |
| metadata | any | No | Whatever JSON you attached at creation, returned unchanged. |
| status | string | Yes | Either open or ended. An ended room cannot mint new tokens. |
| template_id | string or null | No | The template the room resolves roles against, or null if the project has no default template. |
| created_at | timestamp | Yes | |
| started_at | timestamp | No | When the first participant joined. Absent until then. |
| ended_at | timestamp | No | When the room finished. Absent while it is open. |
Errors: 400, 401, 402, 404, 409, 413, 429, 500. Every one carries the same error envelope.
Read one room
GET/v1/rooms/{name}
Reads the room record held by the control plane. It does not call the media plane, so it answers for a room nobody has joined yet.
Project API key.
| Field | Type | Required | Description |
|---|---|---|---|
| name | string · path | Yes | The room name as it was created, without the project prefix. |
Response 200 Room
The room.
| Field | Type | Required | Description |
|---|---|---|---|
| id | uuid | Yes | Velo's internal identifier for the room row. |
| project_id | string | Yes | The project the room belongs to. |
| name | string | Yes | The name you created the room with. |
| namespaced_name | string | Yes | The name the media plane sees, which is the project id, a dot, then the room name. |
| max_participants | integer | Yes | The cap requested at creation. Zero means the plan limit applies. |
| empty_timeout_seconds | integer | Yes | How long the room stays open with nobody in it. Defaults to 300. |
| metadata | any | No | Whatever JSON you attached at creation, returned unchanged. |
| status | string | Yes | Either open or ended. An ended room cannot mint new tokens. |
| template_id | string or null | No | The template the room resolves roles against, or null if the project has no default template. |
| created_at | timestamp | Yes | |
| started_at | timestamp | No | When the first participant joined. Absent until then. |
| ended_at | timestamp | No | When the room finished. Absent while it is open. |
Errors: 401, 404, 429, 500. Every one carries the same error envelope.
End the room and disconnect everyone
DELETE/v1/rooms/{name}
Closes the room on the media plane and marks it ended. No further tokens can be minted for it, so start another call with a new room.
Moderation call. A project API key, or a room token whose role holds the matching permission.
| Field | Type | Required | Description |
|---|---|---|---|
| name | string · path | Yes | The room name as it was created, without the project prefix. |
Response 204
Deleted. No body.
Errors: 400, 401, 403, 404, 429, 500, 503. Every one carries the same error envelope.
Participants and data
Who is in the room right now, and the calls that act on them.
List everyone currently connected
GET/v1/rooms/{name}/participants
Asks the media plane who is in the room right now, then pages the answer. Nobody in the room means an empty list, not a 404.
Project API key.
| Field | Type | Required | Description |
|---|---|---|---|
| name | string · path | Yes | The room name as it was created, without the project prefix. |
| limit | integer · query | No | Page size, between 1 and 200. Defaults to 50. |
| offset | integer · query | No | How many rows to skip. Defaults to 0. |
Response 200 ParticipantPage
A page of participants.
| Field | Type | Required | Description |
|---|---|---|---|
| participants | Participant[] | Yes | Everyone currently connected. |
| limit | integer | Yes | The page size that was applied. |
| offset | integer | Yes | The offset that was applied. |
| has_more | boolean | Yes | True when a further page exists. |
Errors: 400, 401, 404, 429, 500, 503. Every one carries the same error envelope.
Read one participant and their tracks
GET/v1/rooms/{name}/participants/{identity}
Reads one participant's live state, including the attributes Velo set on them and every track they publish.
Project API key.
| Field | Type | Required | Description |
|---|---|---|---|
| name | string · path | Yes | The room name as it was created, without the project prefix. |
| identity | string · path | Yes | The participant identity carried by the token they joined with. |
Response 200 Participant
The participant.
| Field | Type | Required | Description |
|---|---|---|---|
| sid | string | Yes | The participant identifier assigned by the media server for this join. |
| identity | string | Yes | The identity the token was minted for. |
| name | string | Yes | The display name carried by the token. |
| state | string | Yes | joining, joined, active or disconnected. |
| kind | string | Yes | standard, ingress, egress, sip or agent. |
| metadata | string | Yes | The metadata string carried by the token. |
| attributes | map of string | No | Server-set attributes, including velo.role and velo.publish.max_kbps. |
| is_publisher | boolean | Yes | |
| joined_at | timestamp | No | |
| tracks | Track[] | Yes | Every track this participant currently publishes. |
Errors: 400, 401, 404, 429, 500, 503. Every one carries the same error envelope.
Remove a participant
DELETE/v1/rooms/{name}/participants/{identity}
Disconnects one participant. Their token remains valid, so they can rejoin unless you also disable whatever issued it.
Moderation call. A project API key, or a room token whose role holds the matching permission.
| Field | Type | Required | Description |
|---|---|---|---|
| name | string · path | Yes | The room name as it was created, without the project prefix. |
| identity | string · path | Yes | The participant identity carried by the token they joined with. |
Response 204
Deleted. No body.
Errors: 400, 401, 403, 404, 429, 500, 503. Every one carries the same error envelope.
Mute or unmute one track
POST/v1/rooms/{name}/participants/{identity}/mute
Muting needs the mute_others permission and unmuting needs unmute_others, so a role can be allowed to quieten someone without being allowed to switch them back on.
Moderation call. A project API key, or a room token whose role holds the matching permission. Accepts an Idempotency-Key header.
| Field | Type | Required | Description |
|---|---|---|---|
| name | string · path | Yes | The room name as it was created, without the project prefix. |
| identity | string · path | Yes | The participant identity carried by the token they joined with. |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
| track_sid | string | Yes | The track to act on, from the participant's tracks list. |
| muted | boolean | Yes | True to mute, false to unmute. There is no default. |
Response 200 object
The track in its new state.
| Field | Type | Required | Description |
|---|---|---|---|
| track | Track | Yes |
Errors: 400, 401, 403, 404, 413, 429, 500, 503. Every one carries the same error envelope.
Send a data payload into the room
POST/v1/rooms/{name}/send-data
Delivers an arbitrary payload to everyone in the room, or to named identities. The payload is opaque to Velo.
Moderation call. A project API key, or a room token whose role holds the matching permission. Accepts an Idempotency-Key header.
| Field | Type | Required | Description |
|---|---|---|---|
| name | string · path | Yes | The room name as it was created, without the project prefix. |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
| data | string | Yes | The payload, standard base64. |
| kind | string | No | Defaults to reliable. One of reliable, lossy. |
| destination_identities | string[] | No | Who receives it. An empty list means everyone. |
| topic | string | No | An optional topic the client can filter on. |
Response 200 object
The payload was handed to the media plane.
| Field | Type | Required | Description |
|---|---|---|---|
| sent | boolean | Yes | Always true when the call succeeds. |
| bytes | integer | Yes | The decoded payload size. |
Errors: 400, 401, 403, 404, 413, 429, 500, 503. Every one carries the same error envelope.
Move a participant to another role mid-call
POST/v1/rooms/{name}/participants/{identity}/role
Rewrites the participant's grants and velo.role attribute in place, and the media server pushes a refreshed token to them. max_peer_count is not re-checked for someone already in the room. Emits participant.role_changed.
Moderation call. A project API key, or a room token whose role holds the matching permission. Accepts an Idempotency-Key header.
| Field | Type | Required | Description |
|---|---|---|---|
| name | string · path | Yes | The room name as it was created, without the project prefix. |
| identity | string · path | Yes | The participant identity carried by the token they joined with. |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
| role | string | Yes | A role that exists in the room's template. Must match ^[a-zA-Z0-9_-]{1,64}$. |
Response 200 RoleChangeResult
The change, the participant's new state, and a note on what is deferred.
| Field | Type | Required | Description |
|---|---|---|---|
| role_change | RoleChange | Yes | |
| participant | Participant | Yes | |
| applies_live | boolean | Yes | Always true. Grants and the velo.role attribute reach the participant immediately. |
| note | string | Yes | Prose on what does not change until the participant republishes. |
Errors: 400, 401, 403, 404, 413, 429, 500, 503. Every one carries the same error envelope.
Read the role change audit trail
GET/v1/rooms/{name}/role-changes
Every role change made in this room, newest first, with who made it.
Project API key.
| Field | Type | Required | Description |
|---|---|---|---|
| name | string · path | Yes | The room name as it was created, without the project prefix. |
| identity | string · query | No | Only changes affecting this participant. |
| limit | integer · query | No | Page size, between 1 and 200. Defaults to 50. |
| offset | integer · query | No | How many rows to skip. Defaults to 0. |
Response 200 RoleChangePage
A page of role changes.
| Field | Type | Required | Description |
|---|---|---|---|
| role_changes | RoleChange[] | Yes | The role changes for this room, newest first. |
| limit | integer | Yes | The page size that was applied. |
| offset | integer | Yes | The offset that was applied. |
| has_more | boolean | Yes | True when a further page exists. |
Errors: 400, 401, 404, 429, 500. Every one carries the same error envelope.
Tokens
One endpoint. It admits one identity to one room, optionally as a named role.
Mint a join token
POST/v1/tokens
Admits one identity to one room, optionally as a named role. Naming a role replaces permissions with the role's grants, attaches the velo.role and velo.publish.max_kbps attributes, and returns the publish profile. A room that has ended is refused.
Project API key. Accepts an Idempotency-Key header.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
| room | string | Yes | An existing room in this project. Must match ^[a-zA-Z0-9_-]{1,128}$. |
| identity | string | Yes | Who the token is for. Two live participants cannot share an identity. Must match ^[a-zA-Z0-9_.@-]{1,128}$. |
| name | string | No | A display name carried into the room. |
| metadata | string | No | An opaque string handed to every other participant. |
| ttl_seconds | integer | No | How long the token is valid. 0 takes the server default. |
| role | string | No | A role in the room's template. Omit it to fall back to the permissions block. Must match ^[a-zA-Z0-9_-]{1,64}$. |
| permissions | object | No | Used only when no role is named. Each field defaults to true. |
Response 201 Token
The token, the connection url and when it expires.
| Field | Type | Required | Description |
|---|---|---|---|
| token | string | Yes | The room token. Safe to hand to a browser. |
| url | string | Yes | The websocket url the client should connect to. |
| expires_at | timestamp | Yes | |
| role | string | No | The role the token was minted for. Absent when no role was requested. |
| publish_profile | PublishProfile | No |
Errors: 400, 401, 402, 404, 409, 413, 429, 500, 503. Every one carries the same error envelope.
Templates
A reusable bundle of roles and settings that a room points at.
List templates
GET/v1/templates
Every template in the project, each with its roles.
Project API key.
| Field | Type | Required | Description |
|---|---|---|---|
| limit | integer · query | No | Page size, between 1 and 200. Defaults to 50. |
| offset | integer · query | No | How many rows to skip. Defaults to 0. |
Response 200 TemplatePage
A page of templates.
| Field | Type | Required | Description |
|---|---|---|---|
| templates | Template[] | Yes | The templates in this page. |
| limit | integer | Yes | The page size that was applied. |
| offset | integer | Yes | The offset that was applied. |
| has_more | boolean | Yes | True when a further page exists. |
Errors: 400, 401, 429, 500. Every one carries the same error envelope.
Create a template with its roles
POST/v1/templates
Creates a template and every role in one call. Each role is normalised against the documented defaults and then validated, so a role sent as an empty object is still complete.
Project API key. Accepts an Idempotency-Key header.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
| name | string | Yes | Must match ^[a-zA-Z0-9_-]{1,64}$. |
| settings | TemplateSettings | No | |
| roles | map of RoleInput | No | Roles keyed by name. Names that appear here can be referenced from subscribe.to_roles. |
Response 201 Template
The template with its roles resolved.
| Field | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | Must match ^tpl_[0-9a-f]{12}$. |
| project_id | string | Yes | |
| name | string | Yes | Must match ^[a-zA-Z0-9_-]{1,64}$. |
| is_default | boolean | Yes | The default template is used by rooms created without a template_id, and cannot be deleted. |
| settings | TemplateSettings | Yes | |
| roles | map of Role | Yes | Every role in the template, keyed by role name. |
| created_at | timestamp | Yes | |
| updated_at | timestamp | Yes |
Errors: 400, 401, 409, 413, 429, 500. Every one carries the same error envelope.
Read a template and every role in it
GET/v1/templates/{id}
Returns the template with its settings and the full, normalised body of every role.
Project API key.
| Field | Type | Required | Description |
|---|---|---|---|
| id | string · path | Yes | The template id. |
Response 200 Template
The template.
| Field | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | Must match ^tpl_[0-9a-f]{12}$. |
| project_id | string | Yes | |
| name | string | Yes | Must match ^[a-zA-Z0-9_-]{1,64}$. |
| is_default | boolean | Yes | The default template is used by rooms created without a template_id, and cannot be deleted. |
| settings | TemplateSettings | Yes | |
| roles | map of Role | Yes | Every role in the template, keyed by role name. |
| created_at | timestamp | Yes | |
| updated_at | timestamp | Yes |
Errors: 400, 401, 404, 429, 500. Every one carries the same error envelope.
Update the name, settings or named roles
PATCH/v1/templates/{id}
Only the fields you send are touched. Roles sent here are upserted whole; roles you leave out are untouched. Deleting a role needs the role endpoint.
Project API key.
| Field | Type | Required | Description |
|---|---|---|---|
| id | string · path | Yes | The template id. |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
| name | string or null | No | Must match ^[a-zA-Z0-9_-]{1,64}$. |
| settings | TemplateSettings | No | |
| roles | map of RoleInput | No | Roles keyed by name, each replaced whole. |
Response 200 Template
The template after the update.
| Field | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | Must match ^tpl_[0-9a-f]{12}$. |
| project_id | string | Yes | |
| name | string | Yes | Must match ^[a-zA-Z0-9_-]{1,64}$. |
| is_default | boolean | Yes | The default template is used by rooms created without a template_id, and cannot be deleted. |
| settings | TemplateSettings | Yes | |
| roles | map of Role | Yes | Every role in the template, keyed by role name. |
| created_at | timestamp | Yes | |
| updated_at | timestamp | Yes |
Errors: 400, 401, 404, 409, 413, 429, 500. Every one carries the same error envelope.
Delete a template
DELETE/v1/templates/{id}
Refused with 409 if the template is the project default, or if any room still points at it.
Project API key.
| Field | Type | Required | Description |
|---|---|---|---|
| id | string · path | Yes | The template id. |
Response 204
Deleted. No body.
Errors: 400, 401, 404, 409, 429, 500. Every one carries the same error envelope.
Read the region setting and what is allowed
GET/v1/templates/{id}/settings
Returns the stored region together with every region the API accepts and a note on how far region is honoured today.
Project API key.
| Field | Type | Required | Description |
|---|---|---|---|
| id | string · path | Yes | The template id. |
Response 200 TemplateSettingsDetail
The settings.
| Field | Type | Required | Description |
|---|---|---|---|
| region | string | Yes | The region stored on this template. |
| allowed_regions | string[] | Yes | Every region the API will accept. |
| region_enforced | boolean | Yes | False while Velo runs a single media region. |
| region_note | string | Yes | Prose explaining what region does and does not do today. |
Errors: 400, 401, 404, 429, 500. Every one carries the same error envelope.
Update the region setting
PATCH/v1/templates/{id}/settings
Region is stored and validated. Velo runs one media region today, so it does not yet change where traffic goes.
Project API key.
| Field | Type | Required | Description |
|---|---|---|---|
| id | string · path | Yes | The template id. |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
| region | string | Yes | Defaults to auto. One of auto, af-east, eu-west, us-east. |
Response 200 TemplateSettingsDetail
The settings after the update.
| Field | Type | Required | Description |
|---|---|---|---|
| region | string | Yes | The region stored on this template. |
| allowed_regions | string[] | Yes | Every region the API will accept. |
| region_enforced | boolean | Yes | False while Velo runs a single media region. |
| region_note | string | Yes | Prose explaining what region does and does not do today. |
Errors: 400, 401, 404, 413, 429, 500. Every one carries the same error envelope.
Roles
Roles live inside a template and can also be addressed one at a time.
Read one role
GET/v1/templates/{id}/roles/{name}
Returns one role from the template, fully resolved.
Project API key.
| Field | Type | Required | Description |
|---|---|---|---|
| id | string · path | Yes | The template id. |
| name | string · path | Yes | The role name. |
Response 200 Role
The role.
| Field | Type | Required | Description |
|---|---|---|---|
| name | string | Yes | Unique within the template. Must match ^[a-zA-Z0-9_-]{1,64}$. |
| publish | RolePublish | Yes | |
| subscribe | RoleSubscribe | Yes | |
| permissions | RolePermissions | Yes | |
| priority | integer | Yes | 1 is the most important. Defaults to 3. |
| max_peer_count | integer | Yes | -1 admits nobody, 0 is unlimited, a positive number caps concurrent holders. Defaults to 0. |
| hidden | boolean | Yes | A hidden participant is not visible to others in the room. |
Errors: 400, 401, 404, 429, 500. Every one carries the same error envelope.
Replace one role whole
PUT/v1/templates/{id}/roles/{name}
Creates the role if it is new and replaces it otherwise. The body is normalised against the defaults first, so anything you leave out reverts to its default rather than keeping the old value.
Project API key.
| Field | Type | Required | Description |
|---|---|---|---|
| id | string · path | Yes | The template id. |
| name | string · path | Yes | The role name. |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
| name | string | No | If sent it must match the key or path segment the role is filed under. |
| publish | RolePublish | No | |
| subscribe | RoleSubscribe | No | |
| permissions | RolePermissions | No | |
| priority | integer | No | |
| max_peer_count | integer | No | |
| hidden | boolean | No |
Response 200 Role
The role was replaced.
| Field | Type | Required | Description |
|---|---|---|---|
| name | string | Yes | Unique within the template. Must match ^[a-zA-Z0-9_-]{1,64}$. |
| publish | RolePublish | Yes | |
| subscribe | RoleSubscribe | Yes | |
| permissions | RolePermissions | Yes | |
| priority | integer | Yes | 1 is the most important. Defaults to 3. |
| max_peer_count | integer | Yes | -1 admits nobody, 0 is unlimited, a positive number caps concurrent holders. Defaults to 0. |
| hidden | boolean | Yes | A hidden participant is not visible to others in the room. |
Response 201 Role
The role was created.
| Field | Type | Required | Description |
|---|---|---|---|
| name | string | Yes | Unique within the template. Must match ^[a-zA-Z0-9_-]{1,64}$. |
| publish | RolePublish | Yes | |
| subscribe | RoleSubscribe | Yes | |
| permissions | RolePermissions | Yes | |
| priority | integer | Yes | 1 is the most important. Defaults to 3. |
| max_peer_count | integer | Yes | -1 admits nobody, 0 is unlimited, a positive number caps concurrent holders. Defaults to 0. |
| hidden | boolean | Yes | A hidden participant is not visible to others in the room. |
Errors: 400, 401, 404, 413, 429, 500. Every one carries the same error envelope.
Delete one role
DELETE/v1/templates/{id}/roles/{name}
Refused with 409 if another role in the template lists this one in subscribe.to_roles. Remove the reference first.
Project API key.
| Field | Type | Required | Description |
|---|---|---|---|
| id | string · path | Yes | The template id. |
| name | string · path | Yes | The role name. |
Response 204
Deleted. No body.
Errors: 400, 401, 404, 409, 429, 500. Every one carries the same error envelope.
Template destinations
Named recording and streaming targets, configured once on a template and referenced by name when a recording or broadcast starts.
List destinations on a template
GET/v1/templates/{id}/destinations
Returns every destination on the template. This list is not paged.
Project API key.
| Field | Type | Required | Description |
|---|---|---|---|
| id | string · path | Yes | The template id. |
Response 200 DestinationList
The destinations.
| Field | Type | Required | Description |
|---|---|---|---|
| destinations | Destination[] | Yes | Every destination on the template. |
Errors: 400, 401, 404, 429, 500. Every one carries the same error envelope.
Create a named destination
POST/v1/templates/{id}/destinations
Stores an upload target or a set of RTMP urls under a name. Credentials are encrypted at rest, and the call fails with 503 if the deployment has no encryption key. A recording destination needs a plan that includes recording, and an rtmp destination needs a plan that includes live streaming.
Project API key. Accepts an Idempotency-Key header.
| Field | Type | Required | Description |
|---|---|---|---|
| id | string · path | Yes | The template id. |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
| name | string | Yes | How a recording or broadcast will refer to it. Must match ^[a-zA-Z0-9_-]{1,64}$. |
| kind | string | Yes | Immutable once created. One of recording, rtmp. |
| config | RecordingDestinationConfig or RtmpDestinationConfig | Yes | A recording config when kind is recording, an rtmp config when kind is rtmp. |
Response 201 Destination
The destination, with secrets redacted.
| Field | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | Must match ^dst_[0-9a-f]{12}$. |
| template_id | string | Yes | |
| name | string | Yes | Unique within the template. This is the name a recording or broadcast refers to. Must match ^[a-zA-Z0-9_-]{1,64}$. |
| kind | string | Yes | Immutable once created. One of recording, rtmp. |
| config | RecordingDestinationConfig or RtmpDestinationConfig | Yes | A RecordingDestinationConfig when kind is recording, an RtmpDestinationConfig when kind is rtmp. Secrets are redacted. |
| created_at | timestamp | Yes | |
| updated_at | timestamp | Yes |
Errors: 400, 401, 402, 404, 409, 413, 429, 500, 503. Every one carries the same error envelope.
Read one destination, with credentials redacted
GET/v1/templates/{id}/destinations/{name}
The stored secret is never returned. An S3 secret reads back as ***, and an RTMP stream key path reads back as REDACTED.
Project API key.
| Field | Type | Required | Description |
|---|---|---|---|
| id | string · path | Yes | The template id. |
| name | string · path | Yes | The destination name. |
Response 200 Destination
The destination.
| Field | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | Must match ^dst_[0-9a-f]{12}$. |
| template_id | string | Yes | |
| name | string | Yes | Unique within the template. This is the name a recording or broadcast refers to. Must match ^[a-zA-Z0-9_-]{1,64}$. |
| kind | string | Yes | Immutable once created. One of recording, rtmp. |
| config | RecordingDestinationConfig or RtmpDestinationConfig | Yes | A RecordingDestinationConfig when kind is recording, an RtmpDestinationConfig when kind is rtmp. Secrets are redacted. |
| created_at | timestamp | Yes | |
| updated_at | timestamp | Yes |
Errors: 400, 401, 404, 429, 500. Every one carries the same error envelope.
Replace a destination config
PATCH/v1/templates/{id}/destinations/{name}
config is required and replaces the stored configuration whole. Leaving credentials out of an S3 config keeps the stored ones. kind cannot be changed. A destination whose stored kind is recording needs a plan that includes recording, and one whose kind is rtmp needs a plan that includes live streaming.
Project API key.
| Field | Type | Required | Description |
|---|---|---|---|
| id | string · path | Yes | The template id. |
| name | string · path | Yes | The destination name. |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
| kind | string | No | If sent it must equal the stored kind. One of recording, rtmp. |
| config | RecordingDestinationConfig or RtmpDestinationConfig | Yes | The replacement configuration. |
Response 200 Destination
The destination after the update.
| Field | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | Must match ^dst_[0-9a-f]{12}$. |
| template_id | string | Yes | |
| name | string | Yes | Unique within the template. This is the name a recording or broadcast refers to. Must match ^[a-zA-Z0-9_-]{1,64}$. |
| kind | string | Yes | Immutable once created. One of recording, rtmp. |
| config | RecordingDestinationConfig or RtmpDestinationConfig | Yes | A RecordingDestinationConfig when kind is recording, an RtmpDestinationConfig when kind is rtmp. Secrets are redacted. |
| created_at | timestamp | Yes | |
| updated_at | timestamp | Yes |
Errors: 400, 401, 402, 404, 413, 429, 500, 503. Every one carries the same error envelope.
Delete a destination
DELETE/v1/templates/{id}/destinations/{name}
Refused with 409 while a recording or broadcast is still running against it.
Project API key.
| Field | Type | Required | Description |
|---|---|---|---|
| id | string · path | Yes | The template id. |
| name | string · path | Yes | The destination name. |
Response 204
Deleted. No body.
Errors: 400, 401, 404, 409, 429, 500. Every one carries the same error envelope.
Room codes
A short, shareable string bound to one room and one role, for putting people into a call without running a token backend.
List codes for a room
GET/v1/rooms/{name}/codes
Every code issued for the room, including expired and disabled ones.
Project API key.
| Field | Type | Required | Description |
|---|---|---|---|
| name | string · path | Yes | The room name as it was created, without the project prefix. |
| limit | integer · query | No | Page size, between 1 and 200. Defaults to 50. |
| offset | integer · query | No | How many rows to skip. Defaults to 0. |
Response 200 RoomCodePage
A page of codes.
| Field | Type | Required | Description |
|---|---|---|---|
| codes | RoomCode[] | Yes | The codes issued for this room. |
| limit | integer | Yes | The page size that was applied. |
| offset | integer | Yes | The offset that was applied. |
| has_more | boolean | Yes | True when a further page exists. |
Errors: 400, 401, 404, 429, 500. Every one carries the same error envelope.
Create a code
POST/v1/rooms/{name}/codes
Issues a short string bound to one room and one role. Send either expires_at or ttl_seconds, never both. The code is returned in full here and nowhere else.
Project API key. Accepts an Idempotency-Key header.
| Field | Type | Required | Description |
|---|---|---|---|
| name | string · path | Yes | The room name as it was created, without the project prefix. |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
| role | string | Yes | A role that exists in the room's template. Must match ^[a-zA-Z0-9_-]{1,64}$. |
| expires_at | timestamp | No | Between 60 seconds and 30 days from now. |
| ttl_seconds | integer | No | An alternative to expires_at. Defaults to 24 hours. |
| max_uses | integer or null | No | Omit for no cap. |
| identity | string | No | Locks the code to one identity, ignoring whatever the holder sends. Must match ^[a-zA-Z0-9_.@-]{1,128}$. |
Response 201 RoomCode
The code.
| Field | Type | Required | Description |
|---|---|---|---|
| id | uuid | Yes | |
| project_id | string | Yes | |
| room | string | Yes | The room the code admits the holder to. |
| role | string | Yes | The role the minted token carries. |
| code | string | Yes | The shareable string. This is the only place it is returned in full. |
| identity | string or null | No | When set, the code always mints for this identity and ignores the one the holder sends. |
| expires_at | timestamp | Yes | |
| max_uses | integer or null | No | Null means no cap. |
| uses | integer | Yes | How many times the code has been exchanged. |
| disabled | boolean | Yes | |
| created_at | timestamp | Yes | |
| updated_at | timestamp | Yes |
Errors: 400, 401, 404, 413, 429, 500. Every one carries the same error envelope.
Disable a code
DELETE/v1/codes/{code}
Takes the code out of use immediately. Tokens already minted from it keep working until they expire.
Project API key.
| Field | Type | Required | Description |
|---|---|---|---|
| code | string · path | Yes | The code string. |
Response 204
Deleted. No body.
Errors: 401, 404, 429, 500. Every one carries the same error envelope.
Exchange a code for a room token
POST/v1/codes/{code}/exchange
The one route in the API that takes no credential, because the whole point of a code is that the holder has none. It is rate limited per code and per client address. A disabled, expired, exhausted or unknown code all fail identically with 404 code_not_found, so the code space cannot be probed.
No credential. This route is open and rate limited.
| Field | Type | Required | Description |
|---|---|---|---|
| code | string · path | Yes | The code string. |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
| identity | string | No | Who to mint for. Ignored when the code is locked to an identity. Must match ^[a-zA-Z0-9_.@-]{1,128}$. |
Response 201 Token
The same body as POST /v1/tokens, ready to hand straight to a client.
| Field | Type | Required | Description |
|---|---|---|---|
| token | string | Yes | The room token. Safe to hand to a browser. |
| url | string | Yes | The websocket url the client should connect to. |
| expires_at | timestamp | Yes | |
| role | string | No | The role the token was minted for. Absent when no role was requested. |
| publish_profile | PublishProfile | No |
Errors: 400, 402, 404, 409, 413, 429, 500, 503. Every one carries the same error envelope.
Recordings
Recording is started against a room and then tracked by its egress id.
List recordings for one room
GET/v1/rooms/{name}/recordings
Every recording started against this room, newest first.
Project API key.
| Field | Type | Required | Description |
|---|---|---|---|
| name | string · path | Yes | The room name as it was created, without the project prefix. |
| limit | integer · query | No | Page size, between 1 and 200. Defaults to 50. |
| offset | integer · query | No | How many rows to skip. Defaults to 0. |
Response 200 RecordingPage
A page of recordings.
| Field | Type | Required | Description |
|---|---|---|---|
| recordings | Recording[] | Yes | The recordings in this page. |
| limit | integer | Yes | The page size that was applied. |
| offset | integer | Yes | The offset that was applied. |
| has_more | boolean | Yes | True when a further page exists. |
Errors: 400, 401, 404, 429, 500. Every one carries the same error envelope.
Start recording a room
POST/v1/rooms/{name}/recordings
Starts an egress against the room and returns the recording immediately, before the file exists. Poll the recording or listen for recording.ended. Without a destination the file lands in the project's local recording directory. Recording is not on every plan: a project whose plan does not include it is refused with 402 feature_not_in_plan, naming recording in field. Listing, reading and stopping recordings stay available on every plan.
Moderation call. A project API key, or a room token whose role holds the matching permission. Accepts an Idempotency-Key header.
| Field | Type | Required | Description |
|---|---|---|---|
| name | string · path | Yes | The room name as it was created, without the project prefix. |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
| type | string | Yes | What to record. One of room_composite, track, participant. |
| track_id | string | No | Required when type is track, rejected otherwise. |
| identity | string | No | Required when type is participant, rejected otherwise. |
| layout | string | No | room_composite only. Defaults to speaker. One of speaker, grid. |
| audio_only | boolean | No | room_composite only. Mutually exclusive with video_only. |
| video_only | boolean | No | room_composite only. |
| filepath | string | No | A relative name inside the project recording directory. Defaults to a timestamped .mp4. |
| destination | string | No | The name of a recording destination on the room's template. |
Response 201 Recording
The recording, in its starting state.
| Field | Type | Required | Description |
|---|---|---|---|
| id | uuid | Yes | |
| project_id | string | Yes | |
| room | string | Yes | |
| namespaced_room | string | Yes | |
| egress_id | string | Yes | The handle every later call uses. |
| type | string | Yes | One of room_composite, track, participant. |
| status | string | Yes | One of starting, active, ending, complete, failed, aborted, limit_reached. |
| filepath | string | Yes | Where the file is written, inside the project's recording directory. |
| destination | string | No | The template destination the upload used, if one was named. |
| destination_id | string | No | The id of that destination. |
| started_at | timestamp | Yes | |
| ended_at | timestamp | No | |
| error | string | No | Set when the status is failed. |
| created_at | timestamp | Yes |
Errors: 400, 401, 402, 403, 404, 409, 413, 429, 500, 503. Every one carries the same error envelope.
List recordings across the project
GET/v1/recordings
Every recording in the project, filterable by room, status and start time.
Project API key.
| Field | Type | Required | Description |
|---|---|---|---|
| room | string · query | No | Only recordings for this room. |
| status | string · query | No | One of starting, active, ending, complete, failed, aborted, limit_reached. |
| after | timestamp · query | No | Only rows started at or after this RFC 3339 timestamp. |
| before | timestamp · query | No | Only rows started before this RFC 3339 timestamp. |
| limit | integer · query | No | Page size, between 1 and 200. Defaults to 50. |
| offset | integer · query | No | How many rows to skip. Defaults to 0. |
Response 200 RecordingPage
A page of recordings.
| Field | Type | Required | Description |
|---|---|---|---|
| recordings | Recording[] | Yes | The recordings in this page. |
| limit | integer | Yes | The page size that was applied. |
| offset | integer | Yes | The offset that was applied. |
| has_more | boolean | Yes | True when a further page exists. |
Errors: 400, 401, 429, 500. Every one carries the same error envelope.
Read one recording
GET/v1/recordings/{egress_id}
Reads the recording. While it is still running Velo asks the media plane for fresh status and writes it back, so the answer is current rather than cached.
Project API key.
| Field | Type | Required | Description |
|---|---|---|---|
| egress_id | string · path | Yes | The egress id returned when the recording or broadcast started. |
Response 200 Recording
The recording.
| Field | Type | Required | Description |
|---|---|---|---|
| id | uuid | Yes | |
| project_id | string | Yes | |
| room | string | Yes | |
| namespaced_room | string | Yes | |
| egress_id | string | Yes | The handle every later call uses. |
| type | string | Yes | One of room_composite, track, participant. |
| status | string | Yes | One of starting, active, ending, complete, failed, aborted, limit_reached. |
| filepath | string | Yes | Where the file is written, inside the project's recording directory. |
| destination | string | No | The template destination the upload used, if one was named. |
| destination_id | string | No | The id of that destination. |
| started_at | timestamp | Yes | |
| ended_at | timestamp | No | |
| error | string | No | Set when the status is failed. |
| created_at | timestamp | Yes |
Errors: 400, 401, 404, 429, 500, 503. Every one carries the same error envelope.
Stop a recording
POST/v1/recordings/{egress_id}/stop
Asks the media plane to end the egress. The status moves to ending at least, and the file is finalised shortly after.
Moderation call. A project API key, or a room token whose role holds the matching permission. Accepts an Idempotency-Key header.
| Field | Type | Required | Description |
|---|---|---|---|
| egress_id | string · path | Yes | The egress id returned when the recording or broadcast started. |
Response 200 Recording
The recording after the stop.
| Field | Type | Required | Description |
|---|---|---|---|
| id | uuid | Yes | |
| project_id | string | Yes | |
| room | string | Yes | |
| namespaced_room | string | Yes | |
| egress_id | string | Yes | The handle every later call uses. |
| type | string | Yes | One of room_composite, track, participant. |
| status | string | Yes | One of starting, active, ending, complete, failed, aborted, limit_reached. |
| filepath | string | Yes | Where the file is written, inside the project's recording directory. |
| destination | string | No | The template destination the upload used, if one was named. |
| destination_id | string | No | The id of that destination. |
| started_at | timestamp | Yes | |
| ended_at | timestamp | No | |
| error | string | No | Set when the status is failed. |
| created_at | timestamp | Yes |
Errors: 400, 401, 403, 404, 429, 500, 503. Every one carries the same error envelope.
Ingress
Publish into a room from OBS Studio or any RTMP or WHIP encoder. The stream joins as an ordinary participant.
Create an ingress and get its stream key
POST/v1/rooms/{name}/ingress
Gives you a publish url and a stream key for OBS Studio or any RTMP or WHIP encoder. The stream joins the room as an ordinary participant under the identity you name. The key is returned once and only a hash is kept. Live streaming is not on every plan: a project whose plan does not include it is refused with 402 feature_not_in_plan, naming streaming in field. Listing, reading and deleting an ingress stay available on every plan.
Project API key.
| Field | Type | Required | Description |
|---|---|---|---|
| name | string · path | Yes | The room name as it was created, without the project prefix. |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
| input_type | string | Yes | One of rtmp, whip. |
| participant_identity | string | Yes | The identity the stream joins as. Must match ^[a-zA-Z0-9_.@-]{1,128}$. |
| participant_name | string | No | A display name for the stream. |
| enable_transcoding | boolean or null | No | Leave unset to take the media plane's default for the input type. |
Response 201 IngressCreated
The ingress, including the stream key.
| Field | Type | Required | Description |
|---|---|---|---|
| id | uuid | Yes | |
| project_id | string | Yes | |
| room | string | Yes | |
| namespaced_room | string | Yes | |
| ingress_id | string | Yes | |
| input_type | string | Yes | One of rtmp, whip. |
| participant_identity | string | Yes | The identity the incoming stream joins the room as. |
| participant_name | string | No | |
| url | string | Yes | The publish url with the stream key replaced by REDACTED. |
| status | string | Yes | One of inactive, buffering, publishing, error, complete, deleted. |
| error | string | No | |
| created_at | timestamp | Yes | |
| updated_at | timestamp | Yes | |
| deleted_at | timestamp | No | |
| stream_key | string | Yes | Returned once, on creation. Velo keeps only a hash of it, so it cannot be read back. |
Errors: 400, 401, 402, 404, 409, 413, 429, 500, 503. Every one carries the same error envelope.
List ingresses
GET/v1/ingress
Every ingress in the project, filterable by room, status and input type.
Project API key.
| Field | Type | Required | Description |
|---|---|---|---|
| room | string · query | No | Only ingresses for this room. |
| status | string · query | No | One of inactive, buffering, publishing, error, complete, deleted. |
| input_type | string · query | No | rtmp or whip. |
| limit | integer · query | No | Page size, between 1 and 200. Defaults to 50. |
| offset | integer · query | No | How many rows to skip. Defaults to 0. |
Response 200 IngressPage
A page of ingresses.
| Field | Type | Required | Description |
|---|---|---|---|
| ingresses | Ingress[] | Yes | The ingresses in this page. |
| limit | integer | Yes | The page size that was applied. |
| offset | integer | Yes | The offset that was applied. |
| has_more | boolean | Yes | True when a further page exists. |
Errors: 400, 401, 429, 500. Every one carries the same error envelope.
Read one ingress
GET/v1/ingress/{ingress_id}
Reads the ingress and, unless it has been deleted, refreshes its status from the media plane first.
Project API key.
| Field | Type | Required | Description |
|---|---|---|---|
| ingress_id | string · path | Yes | The ingress id. |
Response 200 Ingress
The ingress.
| Field | Type | Required | Description |
|---|---|---|---|
| id | uuid | Yes | |
| project_id | string | Yes | |
| room | string | Yes | |
| namespaced_room | string | Yes | |
| ingress_id | string | Yes | |
| input_type | string | Yes | One of rtmp, whip. |
| participant_identity | string | Yes | The identity the incoming stream joins the room as. |
| participant_name | string | No | |
| url | string | Yes | The publish url with the stream key replaced by REDACTED. |
| status | string | Yes | One of inactive, buffering, publishing, error, complete, deleted. |
| error | string | No | |
| created_at | timestamp | Yes | |
| updated_at | timestamp | Yes | |
| deleted_at | timestamp | No |
Errors: 400, 401, 404, 429, 500, 503. Every one carries the same error envelope.
Delete an ingress
DELETE/v1/ingress/{ingress_id}
Retires the endpoint and invalidates its stream key. The row stays with status deleted so history is not lost.
Project API key.
| Field | Type | Required | Description |
|---|---|---|---|
| ingress_id | string · path | Yes | The ingress id. |
Response 204
Deleted. No body.
Errors: 400, 401, 404, 429, 500, 503. Every one carries the same error envelope.
Broadcasts
Push a room out to RTMP destinations such as YouTube or Twitch.
Start a broadcast
POST/v1/rooms/{name}/broadcasts
Pushes the composited room out to RTMP destinations such as YouTube or Twitch. Send urls inline or name a template destination, never both. Stream keys are redacted everywhere they are read back. Live streaming is not on every plan: a project whose plan does not include it is refused with 402 feature_not_in_plan, naming streaming in field. Listing, reading and stopping broadcasts stay available on every plan.
Moderation call. A project API key, or a room token whose role holds the matching permission. Accepts an Idempotency-Key header.
| Field | Type | Required | Description |
|---|---|---|---|
| name | string · path | Yes | The room name as it was created, without the project prefix. |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
| urls | string[] | No | Between one and eight rtmp:// or rtmps:// urls. Required unless destination is given. |
| destination | string | No | The name of an rtmp destination on the room's template. |
| layout | string | No | Defaults to grid. One of grid, speaker. |
| audio_only | boolean | No |
Response 201 Broadcast
The broadcast, in its starting state.
| Field | Type | Required | Description |
|---|---|---|---|
| id | uuid | Yes | |
| project_id | string | Yes | |
| room | string | Yes | |
| namespaced_room | string | Yes | |
| egress_id | string | Yes | |
| urls | string[] | Yes | The destination urls with the stream key path replaced by REDACTED. |
| status | string | Yes | One of starting, active, ending, complete, failed, aborted, limit_reached. |
| destination | string | No | The template destination used, if one was named. |
| destination_id | string | No | |
| started_at | timestamp | Yes | |
| ended_at | timestamp | No | |
| error | string | No | |
| created_at | timestamp | Yes |
Errors: 400, 401, 402, 403, 404, 409, 413, 429, 500, 503. Every one carries the same error envelope.
List broadcasts
GET/v1/broadcasts
Every broadcast in the project, filterable by room, status and start time.
Project API key.
| Field | Type | Required | Description |
|---|---|---|---|
| room | string · query | No | Only broadcasts for this room. |
| status | string · query | No | One of starting, active, ending, complete, failed, aborted, limit_reached. |
| after | timestamp · query | No | Only rows started at or after this RFC 3339 timestamp. |
| before | timestamp · query | No | Only rows started before this RFC 3339 timestamp. |
| limit | integer · query | No | Page size, between 1 and 200. Defaults to 50. |
| offset | integer · query | No | How many rows to skip. Defaults to 0. |
Response 200 BroadcastPage
A page of broadcasts.
| Field | Type | Required | Description |
|---|---|---|---|
| broadcasts | Broadcast[] | Yes | The broadcasts in this page. |
| limit | integer | Yes | The page size that was applied. |
| offset | integer | Yes | The offset that was applied. |
| has_more | boolean | Yes | True when a further page exists. |
Errors: 400, 401, 429, 500. Every one carries the same error envelope.
Read one broadcast
GET/v1/broadcasts/{egress_id}
Reads the broadcast, refreshing its status from the media plane while it is still running.
Project API key.
| Field | Type | Required | Description |
|---|---|---|---|
| egress_id | string · path | Yes | The egress id returned when the recording or broadcast started. |
Response 200 Broadcast
The broadcast.
| Field | Type | Required | Description |
|---|---|---|---|
| id | uuid | Yes | |
| project_id | string | Yes | |
| room | string | Yes | |
| namespaced_room | string | Yes | |
| egress_id | string | Yes | |
| urls | string[] | Yes | The destination urls with the stream key path replaced by REDACTED. |
| status | string | Yes | One of starting, active, ending, complete, failed, aborted, limit_reached. |
| destination | string | No | The template destination used, if one was named. |
| destination_id | string | No | |
| started_at | timestamp | Yes | |
| ended_at | timestamp | No | |
| error | string | No | |
| created_at | timestamp | Yes |
Errors: 400, 401, 404, 429, 500, 503. Every one carries the same error envelope.
Stop a broadcast
POST/v1/broadcasts/{egress_id}/stop
Ends the egress. The status moves to ending at least, and settles once the media plane confirms.
Moderation call. A project API key, or a room token whose role holds the matching permission. Accepts an Idempotency-Key header.
| Field | Type | Required | Description |
|---|---|---|---|
| egress_id | string · path | Yes | The egress id returned when the recording or broadcast started. |
Response 200 Broadcast
The broadcast after the stop.
| Field | Type | Required | Description |
|---|---|---|---|
| id | uuid | Yes | |
| project_id | string | Yes | |
| room | string | Yes | |
| namespaced_room | string | Yes | |
| egress_id | string | Yes | |
| urls | string[] | Yes | The destination urls with the stream key path replaced by REDACTED. |
| status | string | Yes | One of starting, active, ending, complete, failed, aborted, limit_reached. |
| destination | string | No | The template destination used, if one was named. |
| destination_id | string | No | |
| started_at | timestamp | Yes | |
| ended_at | timestamp | No | |
| error | string | No | |
| created_at | timestamp | Yes |
Errors: 400, 401, 403, 404, 429, 500, 503. Every one carries the same error envelope.
Sessions
One row per participant per join, with the role they joined as and how long they stayed.
List sessions
GET/v1/sessions
One row per participant per join, with the role they joined as, how long they stayed and how many tracks they published.
Project API key.
| Field | Type | Required | Description |
|---|---|---|---|
| room | string · query | No | Only sessions in this room. |
| identity | string · query | No | Only sessions for this participant identity. |
| role | string · query | No | Only sessions joined under this role. |
| active | boolean · query | No | true for sessions still connected, false for finished ones. |
| after | timestamp · query | No | Only sessions joined at or after this RFC 3339 timestamp. |
| before | timestamp · query | No | Only sessions joined before this RFC 3339 timestamp. |
| limit | integer · query | No | Page size, between 1 and 200. Defaults to 50. |
| offset | integer · query | No | How many rows to skip. Defaults to 0. |
Response 200 SessionPage
A page of sessions.
| Field | Type | Required | Description |
|---|---|---|---|
| sessions | Session[] | Yes | The sessions in this page. |
| limit | integer | Yes | The page size that was applied. |
| offset | integer | Yes | The offset that was applied. |
| has_more | boolean | Yes | True when a further page exists. |
Errors: 400, 401, 429, 500. Every one carries the same error envelope.
Read one session and the tracks it published
GET/v1/sessions/{id}
The session with every track the participant published, each with when it started and when it ended.
Project API key.
| Field | Type | Required | Description |
|---|---|---|---|
| id | uuid · path | Yes | The session id. |
Response 200 SessionDetail
The session and its tracks.
| Field | Type | Required | Description |
|---|---|---|---|
| id | uuid | Yes | One row per participant per join. |
| room | string | Yes | |
| participant_identity | string | Yes | |
| participant_sid | string | Yes | |
| role | string or null | Yes | The role the participant joined as, or null when the token carried none. |
| joined_at | timestamp | Yes | |
| left_at | timestamp | No | Absent while the participant is still connected. |
| duration_seconds | integer | Yes | Measured to left_at, or to now while the session is open. |
| track_count | integer | Yes | How many tracks the participant published during the session. |
| namespaced_room | string | Yes | |
| tracks | SessionTrack[] | Yes | Every track published during the session, in the order they started. |
Errors: 400, 401, 404, 429, 500. Every one carries the same error envelope.
List sessions for one room
GET/v1/rooms/{name}/sessions
The same filters as the project-wide list, scoped to one room.
Project API key.
| Field | Type | Required | Description |
|---|---|---|---|
| name | string · path | Yes | The room name as it was created, without the project prefix. |
| identity | string · query | No | Only sessions for this participant identity. |
| role | string · query | No | Only sessions joined under this role. |
| active | boolean · query | No | true for sessions still connected, false for finished ones. |
| after | timestamp · query | No | Only sessions joined at or after this RFC 3339 timestamp. |
| before | timestamp · query | No | Only sessions joined before this RFC 3339 timestamp. |
| limit | integer · query | No | Page size, between 1 and 200. Defaults to 50. |
| offset | integer · query | No | How many rows to skip. Defaults to 0. |
Response 200 SessionPage
A page of sessions.
| Field | Type | Required | Description |
|---|---|---|---|
| sessions | Session[] | Yes | The sessions in this page. |
| limit | integer | Yes | The page size that was applied. |
| offset | integer | Yes | The offset that was applied. |
| has_more | boolean | Yes | True when a further page exists. |
Errors: 400, 401, 404, 429, 500. Every one carries the same error envelope.
Webhooks
Register receivers, then read every delivery attempt and re-queue the ones that failed.
List endpoints
GET/v1/webhook-endpoints
Every registered receiver. Secrets are not included, because Velo cannot show them again.
Project API key.
| Field | Type | Required | Description |
|---|---|---|---|
| limit | integer · query | No | Page size, between 1 and 200. Defaults to 50. |
| offset | integer · query | No | How many rows to skip. Defaults to 0. |
Response 200 WebhookEndpointPage
A page of endpoints.
| Field | Type | Required | Description |
|---|---|---|---|
| webhook_endpoints | WebhookEndpoint[] | Yes | The endpoints registered on this project. |
| limit | integer | Yes | The page size that was applied. |
| offset | integer | Yes | The offset that was applied. |
| has_more | boolean | Yes | True when a further page exists. |
Errors: 400, 401, 429, 500. Every one carries the same error envelope.
Register an endpoint and receive its signing secret once
POST/v1/webhook-endpoints
Registers a receiver. The url is resolved and checked before it is stored, so an address on a private network is refused. The signing secret is in this response and nowhere else.
Project API key. Accepts an Idempotency-Key header.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
| url | string | Yes | An http or https url that resolves to a public address. |
Response 201 WebhookEndpointCreated
The endpoint and its signing secret.
| Field | Type | Required | Description |
|---|---|---|---|
| id | uuid | Yes | |
| url | string | Yes | |
| secret | string | Yes | The signing secret, returned once and never again. Store it before you close the response. |
| active | boolean | Yes | |
| created_at | timestamp | Yes |
Errors: 400, 401, 413, 429, 500. Every one carries the same error envelope.
Delete an endpoint
DELETE/v1/webhook-endpoints/{id}
Stops delivery to this receiver. Deliveries already queued for it are dropped.
Project API key.
| Field | Type | Required | Description |
|---|---|---|---|
| id | uuid · path | Yes | The endpoint id. |
Response 204
Deleted. No body.
Errors: 400, 401, 404, 429, 500. Every one carries the same error envelope.
List delivery attempts
GET/v1/webhook-deliveries
Every delivery Velo has queued for this project, newest first, with the exact payload, the attempt count and the last error. This is where you look when a receiver stopped hearing from Velo.
Project API key.
| Field | Type | Required | Description |
|---|---|---|---|
| status | string · query | No | pending, delivered or failed. |
| event_type | string · query | No | An event name such as recording.ended. |
| endpoint_id | uuid · query | No | Only deliveries for one endpoint. |
| limit | integer · query | No | Page size, between 1 and 200. Defaults to 50. |
| offset | integer · query | No | How many rows to skip. Defaults to 0. |
Response 200 WebhookDeliveryPage
A page of deliveries.
| Field | Type | Required | Description |
|---|---|---|---|
| webhook_deliveries | WebhookDelivery[] | Yes | The delivery attempts in this page, newest first. |
| limit | integer | Yes | The page size that was applied. |
| offset | integer | Yes | The offset that was applied. |
| has_more | boolean | Yes | True when a further page exists. |
Errors: 400, 401, 429, 500. Every one carries the same error envelope.
Re-queue a failed delivery
POST/v1/webhook-deliveries/{id}/retry
Resets the delivery to pending with the attempt count back at zero, so the full retry schedule runs again. Only a delivery whose status is failed can be retried.
Project API key. Accepts an Idempotency-Key header.
| Field | Type | Required | Description |
|---|---|---|---|
| id | uuid · path | Yes | The delivery id. |
Response 200 WebhookDelivery
The delivery, back in the queue.
| Field | Type | Required | Description |
|---|---|---|---|
| id | uuid | Yes | |
| endpoint_id | uuid | Yes | |
| endpoint_url | string | Yes | |
| event_type | string | Yes | The event name, such as participant.joined. |
| payload | any | Yes | The exact JSON body that was posted, byte for byte the body the signature covers. |
| status | string | Yes | One of pending, delivered, failed. |
| attempts | integer | Yes | How many times Velo has posted this delivery. |
| next_attempt_at | timestamp | No | Present only while the status is pending. |
| delivered_at | timestamp | No | |
| created_at | timestamp | Yes | |
| last_error | string | Yes | The transport or status error from the most recent attempt. |
| response_status | integer or null | Yes | The HTTP status the receiver returned, or null if the request never completed. |
Errors: 400, 401, 404, 409, 429, 500. Every one carries the same error envelope.
Usage and plan
What the project has consumed and what its plan allows.
Participant minutes per day for a period
GET/v1/usage
Daily participant minutes and session counts, with the plan status attached. Defaults to the last 30 days.
Project API key.
| Field | Type | Required | Description |
|---|---|---|---|
| from | string · query | No | First day, YYYY-MM-DD. Defaults to 29 days before to. |
| to | string · query | No | Last day, YYYY-MM-DD. Defaults to today in UTC. |
Response 200 Usage
Usage for the period.
| Field | Type | Required | Description |
|---|---|---|---|
| days | UsageDay[] | Yes | One row per day in the requested range. |
| total_participant_minutes | number | Yes | |
| plan | PlanStatus | Yes |
Errors: 400, 401, 429, 500, 503. Every one carries the same error envelope.
The plan, quota state and current limits
GET/v1/plan
What the project is on, what it has used this period and how many rooms are open right now. features is a sibling of limits and says whether the plan includes recording and live streaming.
Project API key.
Response 200 PlanStatus
The plan status.
| Field | Type | Required | Description |
|---|---|---|---|
| plan_id | string | Yes | |
| display_name | string | Yes | |
| price_ugx | integer | Yes | The monthly price in Ugandan shillings. |
| limits | PlanLimits | Yes | |
| features | PlanFeatures | Yes | |
| quota_state | string | Yes | An exceeded project has used its whole monthly participant-minute block; a suspended project has been stopped for other reasons. Both are refused with 402 on anything that would start work. One of active, suspended, exceeded. |
| period_start | string | Yes | The first day of the current billing period, YYYY-MM-DD. |
| participant_minutes_used | number | Yes | |
| participant_minutes_remaining | number | Yes | |
| active_rooms | integer | Yes |
Errors: 401, 429, 500. Every one carries the same error envelope.
Was this page useful?