You can create a call by providing the call type and an ID:
The call type controls which features are enabled and sets up permissions. Call type settings and permissions can be set from API, or using the Stream Dashboard.
Calls can be used once or multiple times depending on your app. Unless you want to re-use the same call multiple times, the recommended way to pick a call ID is to use a uuid v4 so that each call gets a unique random ID.
You can specify call members who can receive push notification about the call.
It's also possible to store any custom data with the call object.
import uuidfrom getstream.models.call_request import CallRequestcall = client.video.call("default", uuid.uuid4())call.create( data=CallRequest( created_by_id="sacha", ),)# optionally provide additional datacall.create( data=CallRequest( created_by_id="sacha", # note: you can add users as members to calls to support more complex permissions members=[ MemberRequest(user_id="john", role="admin"), MemberRequest(user_id="jack"), ], custom={"color": "blue"}, ),)
The Stream Video API offers a ringing flow that simulates traditional phone calls. This feature lets developers implement call experiences where users can receive incoming calls with ringtones and choose to accept or decline.
You can also show missed call notifications if someone doesn't answer the call on time or is busy in another call.
To start the ringing flow, we need to set the ring flag to true and provide the list of members we want to call. It is important to note that the caller should also be included in the list of members.
Stream API allows reusing call ids (for example, to implement recurring meetings), but this is not something we advise for ring calls. You should create a unique id for every ring call (for example by using UUIDs).
You can set the video flag to true or false (for audio calls), which will control the type of incoming call screen client-side SDKs will display.
// Make sure to use a unique id when creating ring callsconst call = client.video.call("default", crypto.randomUUID());await call.getOrCreate({ ring: true, video: true, // Set it to `false` for audio calls. This is used to decide what type of incoming call screen is displayed client-side data: { created_by_id: "myself", members: [{ user_id: "myself" }, { user_id: "my friend" }], },});
from getstream.models import CallRequest, MemberRequestimport uuid# Make sure to use a unique id when creating ring callsclient.video.call("default", uuid.uuid4()).get_or_create( data=CallRequest( created_by_id=user_id, members=[MemberRequest(user_id=alice.id), MemberRequest(user_id=bob.id)], ), ring=True, video=True, # Set it to `false` for audio calls. This is used to decide what type of incoming call screen is displayed client-side)
import ( "github.com/google/uuid")// Make sure to use a unique id when creating ring callsclient.Video().Call("default", uuid.New().String()).GetOrCreate(ctx, &getstream.GetOrCreateCallRequest{ Ring: getstream.PtrTo(true), Video: getstream.PtrTo(true), // Set it to `false` for audio calls. This is used to decide what type of incoming call screen is displayed client-side Data: &getstream.CallRequest{ CreatedByID: getstream.PtrTo("myself"), Members: []getstream.MemberRequest{ {UserID: "myself"}, {UserID: "my friend"}, }, },})
List<MemberRequest> members = List.of( MemberRequest.builder().userID("myself").build(), MemberRequest.builder().userID("my friend").build());// Make sure to use a unique id when creating ring callsvar call = new Call("default", UUID.randomUUID().toString(), client.video());call.getOrCreate( GetOrCreateCallRequest.builder() .data( CallRequest.builder() .createdByID("myself") .members(members) .build() ) .ring(true) .video(true) // Set it to `false` for audio calls. This is used to decide what type of incoming call screen is displayed client-side .build());
CALL_TYPE='default'# Make sure to use a unique id when creating ring callsCALL_ID=$(uuidgen)curl -X POST "https://video.stream-io-api.com/api/v2/video/call/${CALL_TYPE}/${CALL_ID}?api_key=${API_KEY}" \ -H "Authorization: ${TOKEN}" \ -H "Content-Type: application/json" \ -H "stream-auth-type: jwt" \ -d '{ "ring": true, "video": true, "data": { "created_by_id": "myself", "members": [{ "user_id": "myself" }, { "user_id": "my friend" }] } }'
For more information on ring calls, checout the Ring Calls guide
Setting the notify flag to true will send the call.notification WebSocket event. It's also possible to send push notifications to call members on this event, for more information see the Call Types page.
// or call.create// or call.getawait client.call("default", "test-outgoing-call").getOrCreate({ notify: true, data: { created_by_id: "myself", members: [{ user_id: "myself" }, { user_id: "my friend" }], },});
Call types provide sensible default settings for different use-cases. We provide the following types out of the box:
Default (default) for 1:1 or group calls that use both video and audio
Livestreaming (livestream) to build ultra low latency livestreaming for your app on our global edge network. Broadcast from your phone or RTMP and scale to millions of participants.
Audio room (audio_room) to build audio experiences for your app. You can build basic calling or feature rich experience like Twitter spaces. Audio quality, reliability and scalability is far ahead of competing solutions.
Development (development) This call type comes with almost all permission settings disabled so that it is simpler to get your initial implementation up and running. You should only use this call type early-on during development.
from getstream.models import CallSettingsRequest# update some custom data for this callcall.update(custom={'color': 'red'})# update settings for this callcall.update( settings_override=CallSettingsRequest( screensharing=ScreensharingSettingsRequest( enabled=True, access_request_enabled=True ), ),)
// update some custom data for this callresponse, err := call.Update(ctx, &getstream.UpdateCallRequest{ Custom: map[string]any{"color": "red"},})// update settings for this callresponse, err = call.Update(ctx, &getstream.UpdateCallRequest{ SettingsOverride: &getstream.CallSettingsRequest{ Screensharing: &getstream.ScreensharingSettingsRequest{ Enabled: getstream.PtrTo(true), AccessRequestEnabled: getstream.PtrTo(true), }, },})
You can provide a list of call members, this can be done when you create a call or later on when the call already exists. Please note that call members need to be existing users.
There are two reasons to use call members:
Call membership allows you to have more flexibility when it comes to permissions. The permission system allows you to grant different permissions to users and members, this way one user can be a member on one call or a member on another.
Membership also allows you to grant additional roles to users in a call. It's important to note that this doesn't restrict access, but rather expands capabilities.
You can more information about the roles and permissions here.
// Call members need to be existing userscall.updateCallMembers({ // You can add new members // You can also update the role of existing members update_members: [{ user_id: "sara" }, { user_id: "emily", role: "admin" }],});
# Call members need to be existing users# You can also update the role of existing memberscall.update_call_members( update_members=[ MemberRequest(user_id="sara"), MemberRequest(user_id="emily", role="admin"), ])
// Call members need to be existing users (use `client.UpdateUsers` for that)// You can also update the role of existing membersresponse, err := call.UpdateCallMembers(ctx, &getstream.UpdateCallMembersRequest{ UpdateMembers: []getstream.MemberRequest{ {UserID: "sara"}, {UserID: "emily", Role: getstream.PtrTo("admin")}, },})
// Call members need to be existing users// You can also update the role of existing membersmembers = new ArrayList<MemberRequest>() { { add(MemberRequest.builder().userID("john").role("admin").build()); add(MemberRequest.builder().userID("jane").build()); add(MemberRequest.builder().userID("tom").build()); }};call.updateCallMembers(UpdateCallMembersRequest.builder().updateMembers(members).build());
# You can only add existing members to a callcurl -X POST "https://video.stream-io-api.com/api/v2/video/call/${CALL_TYPE}/${CALL_ID}/members?api_key=${API_KEY}" \ -H "Authorization: ${TOKEN}" \ -H "Content-Type: application/json" \ -H "stream-auth-type: jwt" \ -d '{ "update_members": [ { "user_id": "sara" }, { "user_id": "john", "role": "admin" } ] }'
You can restrict access to a call by updating the Call Type permissions and roles.
A typical use case is to restrict access to a call to a specific set of users -> call members.
By default, all users unless specified otherwise, have the user role.
You can remove the join-call permission from the user role for the given call type scope. This will prevent regular users from joining a call of the given type.
Next, let's ensure that the call_member role has the join-call permission for the given call type scope. This will allow users with the call_member role to join a call of this type.
Once this is set, we can proceed with setting up a call instance and providing the members.
const callTypeName = "default";const callType = (await client.video.listCallTypes()).call_types[callTypeName];// Remove JOIN_CALL permission from user roleconst userGrants = callType.grants["user"].filter( (c) => c !== OwnCapability.JOIN_CALL,);// Make sure JOIN_CALL permission is set for call_member roleconst callMemberGrants = callType.grants["call_member"];if (!callMemberGrants.includes(OwnCapability.JOIN_CALL)) { callMemberGrants.push(OwnCapability.JOIN_CALL);}// Update the call type with the changesawait client.video.updateCallType({ name: callTypeName, grants: { user: userGrants, call_member: callMemberGrants, },});
# Remove join call grant from user role, only include this for call_member rolecurl -X PUT "https://video.stream-io-api.com/api/v2/video/calltypes/${CALL_TYPE_NAME}?api_key=${API_KEY}" \ -H "Authorization: ${TOKEN}" \ -H "Content-Type: application/json" \ -H "stream-auth-type: jwt" \ -d '{ "grants": { "user": [...list all grants for user here], "call_member": [...list all grants for call member here, "join-call"] } }'
Call IDs can be used for multiple calls. For each call, a new Call Session is created. The session begins when the first participant joins and ends after the last participant leaves and the session inactivity timeout period elapses. The default session inactivity timeout is 30 seconds, but it can be configured per call type or for individual calls.
When a call session is started, the call.session_started event will be dispatched. It's also possible to send push notifications to call members on this event, for more information see the Call Types page.
When a call session is ended, the call.session_ended event will be dispatched.
When you need to customize the session timeout for a call type, you can modify the inactivity_timeout_seconds setting. This value determines how long a call session remains active after the last participant leaves before it automatically ends. The timeout can be set between 5 seconds and 15 minutes (900 seconds). Here's how to configure a custom session timeout for different platforms:
# Provide the time in ISO date stringcurl -X POST "https://video.stream-io-api.com/api/v2/video/calls?api_key=${API_KEY}" \ -H "Authorization: ${TOKEN}" \ -H "stream-auth-type: jwt" \ -H "Content-Type: application/json" \ -d '{ "filter_conditions": { "starts_at": { "$gt": "2024-05-10T09:09:03.584Z" } } }'
string, must be formatted as an RFC3339 timestamp (for example 2021-01-15T09:30:20.45Z)
Creation time of the call
$in, $eq, $gt, $gte, $lt, $lte, $exists
updated_at
string, must be formatted as an RFC3339 timestamp (for example 2021-01-15T09:30:20.45Z)
The time of the last update of the call
$in, $eq, $gt, $gte, $lt, $lte, $exists
ended_at
string, must be formatted as an RFC3339 timestamp (for example 2021-01-15T09:30:20.45Z)
The time the call was ended
$in, $eq, $gt, $gte, $lt, $lte, $exists
starts_at
string, must be formatted as an RFC3339 timestamp (for example 2021-01-15T09:30:20.45Z)
The scheduled start time of the call
$in, $eq, $gt, $gte, $lt, $lte, $exists
backstage
boolean
true if the call is in backstage mode
$eq
members
string array
Member user ids
$in
ongoing
boolean
true if there is an ongoing session for the call
$eq
The Stream API allows you to specify filters and ordering for several endpoints. The query syntax is similar to that of Mongoose, however we do not run MongoDB on the backend. Only a subset of the MongoDB operations are supported.
Name
Description
Example
$eq
Matches values that are equal to a specified value.
{ "key": { "$eq": "value" } } or the simplest form { "key": "value" }
$q
Full text search (matches values where the whole text value matches the specified value)
{ "key": { "$q": "value } }
$gt
Matches values that are greater than a specified value.
{ "key": { "$gt": 4 } }
$gte
Matches values that are greater than or equal to a specified value.
{ "key": { "$gte": 4 } }
$lt
Matches values that are less than a specified value.
{ "key": { "$lt": 4 } }
$lte
Matches values that are less than or equal to a specified value.
{ "key": { "$lte": 4 } }
$in
Matches any of the values specified in an array.
{ "key": { "$in": [ 1, 2, 4 ] } }
$exists
Mathces values that either have (when set to true) or not have (when set to false) certain attributes
{ "key": { "$exists": true } }
$autocomplete
Mathces values that start with the specified string value
{ "key": { "$autocomplete": "value" } }
It's also possible to combine filter expressions with the following operators:
Search in custom membership data, example syntax: {'custom.color': {$eq: 'red'}}
$in, $eq, $gt, $gte, $lt, $lte, $exists
created_at
string, must be formatted as an RFC3339 timestamp (for example 2021-01-15T09:30:20.45Z)
Creation time of the user
$in, $eq, $gt, $gte, $lt, $lte, $exists
updated_at
string, must be formatted as an RFC3339 timestamp (for example 2021-01-15T09:30:20.45Z)
The time of the last update of the user
$in, $eq, $gt, $gte, $lt, $lte, $exists
The Stream API allows you to specify filters and ordering for several endpoints. The query syntax is similar to that of Mongoose, however we do not run MongoDB on the backend. Only a subset of the MongoDB operations are supported.
Name
Description
Example
$eq
Matches values that are equal to a specified value.
{ "key": { "$eq": "value" } } or the simplest form { "key": "value" }
$q
Full text search (matches values where the whole text value matches the specified value)
{ "key": { "$q": "value } }
$gt
Matches values that are greater than a specified value.
{ "key": { "$gt": 4 } }
$gte
Matches values that are greater than or equal to a specified value.
{ "key": { "$gte": 4 } }
$lt
Matches values that are less than a specified value.
{ "key": { "$lt": 4 } }
$lte
Matches values that are less than or equal to a specified value.
{ "key": { "$lte": 4 } }
$in
Matches any of the values specified in an array.
{ "key": { "$in": [ 1, 2, 4 ] } }
$exists
Mathces values that either have (when set to true) or not have (when set to false) certain attributes
{ "key": { "$exists": true } }
$autocomplete
Mathces values that start with the specified string value
{ "key": { "$autocomplete": "value" } }
It's also possible to combine filter expressions with the following operators:
This endpoint allows you to retrieve a list of connected participants to an active call and filter the list by ID and by the type of track published. It is recommended to NOT poll this endpoint. Instead, you can use this endpoint together with webhooks and websocket events (see watching calls).
This endpoint only returns participants that are currently connected. You cannot use this endpoint to fetch the historical participants of a call or use it for a call that is ended/inactive.
The endpoint also returns the total count of participants connected to the call, including the participants that were not selected by the filters or limits of the call.
It's possible to send any custom event for a call:
// send a custom event to all users watching the callcall.sendCallEvent({ custom: { "render-animation": "balloons", }, user_id: "john",});
# send a custom event to all users watching the callcall.send_call_event(user_id=user.id, custom={"render-animation": "balloons"})
// send a custom event to all users watching the callcall.SendCallEvent(ctx, &getstream.SendCallEventRequest{ Custom: &map[string]interface{}{ "render-animation": "balloons", }, UserID: PtrTo("john"),})
// send a custom event to all users watching the callcall.sendCallEvent(SendCallEventRequest.builder() .custom(Map.of("render-animation", "balloons")) .userID("john") .build());
You can pin the video of a participant in a call session. The SDKs will make sure that the pinned participant is displayed in a prominent location in the call layout for all participants. You can also unpin a pinned participant if you no longer want to highlight them.
# Pin video for all participantscall.video_pin(session_id="session-id", user_id="user-id-to-pin")# Unpin video for all participantscall.video_unpin(session_id="session-id", user_id="user-id-to-unpin")
// Pin video for all participantscall.VideoPin(ctx, &getstream.PinRequest{ SessionID: "session-id", UserID: "user-id-to-pin",})// Unpin video for all participantscall.VideoUnpin(ctx, &getstream.UnpinRequest{ SessionID: "session-id", UserID: "user-id-to-unpin",})
// Pin video for all participantscall.videoPin(VideoPinRequest.builder().userID("user-id-to-pin").sessionID("session-id").build());// Unpin video for all participantscall.videoUnpin(VideoUnpinRequest.builder().userID("user-id-to-unpin").sessionID("session-id").build());
You can limit the maximum duration for calls. This limit can be set for individual calls or set to a default value for all calls with the same type.
When set, users will be automatically removed from a call when the time runs out and the call will be marked as ended.
Please note that by default, most call types are configured so that users can join ended calls. To ensure session timers work correctly, the JoinEndedCall permission should be disabled at the call level. This can be done from the dashboard.
It is possible to update a call and extend the session time. In that case a call.updated event is sent to all connected clients so that their UI can be updated accordingly.