createCall method
Future<CreateRealtimeCallResponse>
createCall({
- required String sdp,
- RealtimeSessionType type = RealtimeSessionType.realtime,
- RealtimeModel? model,
- String? instructions,
- dynamic maxOutputTokens,
- List<
Modality> ? outputModalities, - RealtimeSessionAudio? audio,
- List<
RealtimeFunctionTool> ? tools, - ToolChoice? toolChoice,
- Tracing? tracing,
- List<
String> ? include, - Map<
String, dynamic> ? prompt, - RealtimeTruncation? truncation,
- double? temperature,
Create a new Realtime API call over WebRTC and receive the SDP answer.
Mirrors: POST /v1/realtime/calls
Required:
sdp
: the WebRTC SDP offer generated by the caller.
Session configuration:
- Provide either a full
sessionJson
that matches the API docs or use the convenience params (same semantics ascreateRealtimeClientSecret
).
Notes:
session.type
must be"realtime"
for Realtime sessions (default here).outputModalities
cannot request both"audio"
and"text"
at once.- The response body is plain text (application/sdp) with the SDP answer.
- The
Location
header includes the call ID for follow‑ups (monitoring WebSocket,/accept
,/hangup
, etc.).
Implementation
Future<CreateRealtimeCallResponse> createCall({
// SDP offer
required String sdp,
// Convenience fields (used only if `sessionJson` is null or empty)
RealtimeSessionType type = RealtimeSessionType.realtime,
RealtimeModel? model, // e.g., RealtimeModel.gptRealtime
String? instructions,
dynamic maxOutputTokens, // int | "inf"
List<Modality>? outputModalities, // ["audio"] | ["text"]
RealtimeSessionAudio? audio, // input/output audio config
List<RealtimeFunctionTool>? tools,
ToolChoice? toolChoice,
Tracing? tracing, // "auto" or object
List<String>? include, // e.g. ["item.input_audio_transcription.logprobs"]
Map<String, dynamic>? prompt, // {id, variables?, version?}
RealtimeTruncation? truncation, // "auto" or {type:"retention_ratio", retention_ratio:0.5}
double? temperature,
}) async {
final session = <String, dynamic>{
'type': type.toJson(),
if (model != null) 'model': model.toJson(),
if (instructions != null) 'instructions': instructions,
if (maxOutputTokens != null) 'max_output_tokens': maxOutputTokens,
if (outputModalities != null) 'output_modalities': outputModalities.map((m) => m.toJson()).toList(),
if (tools != null) 'tools': tools.map((t) => t.toJson()).toList(),
if (toolChoice != null) 'tool_choice': toolChoice.toJson(),
if (tracing != null) 'tracing': tracing.toJson(),
if (include != null) 'include': include,
if (prompt != null) 'prompt': prompt,
if (truncation != null) 'truncation': truncation.toJson(),
if (audio != null) 'audio': audio.toJson(),
if (temperature != null) 'temperature': temperature,
};
final request = http.MultipartRequest("POST", baseUrl.resolve("realtime/calls"))
..files.add(http.MultipartFile.fromBytes(
"sdp",
utf8.encode(sdp),
contentType: MediaType("application", "sdp"),
))
..files.add(http.MultipartFile.fromBytes(
"session",
utf8.encode(jsonEncode(
session,
)),
contentType: MediaType("application", "json"),
));
request.headers.addAll(getHeaders({}) ?? {});
final streamedResponse = await request.send();
final res = await http.Response.fromStream(streamedResponse);
// The spec returns 201 Created with the SDP **answer** in the body.
if (res.statusCode != 201 && res.statusCode != 200) {
throw OpenAIRequestException.fromHttpResponse(res);
}
// Extract the Location header (case-insensitive) to get the call ID.
String? locationHeader;
res.headers.forEach((k, v) {
if (k.toLowerCase() == 'location') locationHeader = v;
});
Uri? locationUri;
String? callId;
if (locationHeader != null && locationHeader!.isNotEmpty) {
locationUri = Uri.tryParse(locationHeader!);
// Try to parse `/v1/realtime/calls/<id>` regardless of absolute/relative form.
final String path = locationUri?.path ?? locationHeader!;
final match = RegExp(r'/realtime/calls/([^/?#]+)').firstMatch(path);
if (match != null) callId = match.group(1);
}
return CreateRealtimeCallResponse(
sdpAnswer: res.body, // plain text SDP answer
callId: callId,
location: locationUri,
);
}