> For complete lifecycle-event payload schemas and examples, use the canonical overview: Session /api/sessions; Room lifecycle, members, and room playback /api/rooms; Session playback /api/playback; Recording /api/recording; DTMF /api/keypad-input; Messages /api/messages; WebSocket Session /api/web-socket. Use /api/event-delivery for transport behavior. Endpoint pages name relevant events, but these overviews are the canonical references.

# Start recording

POST https://api.telekesher.dev/v1/sessions/{uuid}/recordings
Content-Type: application/json

**Rate limit: Per command** · **Burst:** 4 · **Refill:** 1 req/s

Start a [Recording](/api/recording) of the call.

The `202 Accepted` response confirms that the command was admitted. It does not confirm that recording has started or that recorded audio is available. Retain the returned `recording_uuid` to address this recording, and use the returned `operation_uuid` to correlate the recording lifecycle webhooks below.

Send `POST /v1/sessions/{uuid}/recordings` as soon as you receive the Session's `session_uuid`. Choose the capture boundary with `start_only_after_answer`:

* By default, inbound and outbound phone Sessions start recording as soon as the current media path permits, including available early media.
* Set `start_only_after_answer` to `true` to durably wait for the canonical answer instead. If a waiting Session ends first, `recording.failed` is emitted.

Choose independently for each recording whether to include redacted periods and periods without recordable audio. Omit `include_gaps` or set it to `false` to remove those gaps from the artifact. Set it to `true` to include the gaps as silence.

A Session may have multiple active recordings, including duplicate `both` + `mono` recordings. Voice activity events and their detector configuration are optional and independent for every recording.

Output is 8 kHz mono mixed WAV (both legs combined). Format is not configurable.

**Triggered webhooks:**

* `recording.became_available` carries signed `media_url` and
  `live_url` capabilities; `recording.failed` is emitted instead if the recording
  does not become available.
* When `enable_voice_activity_events` is true,
  `recording.speech.started` and `recording.speech.ended` report
  customer speech transitions for the lifetime of this recording.
  Sensitivity and speech/silence confirmation durations are configurable.

Use `media_url` to start at byte zero and follow the growing Recording until
EOF. After finalization, the same URL serves a finite, range-capable WAV. Use
`live_url` to join near the current position.
Anyone with either URL can access its capability until it expires.
By default, it expires 24 hours after the lifecycle event is emitted.

> **Tip: Minimize confirmation playback delay.** If you plan to play a recording back
> to the caller for confirmation, start streaming the `media_url` from the
> beginning into an S3 multipart upload as soon as
> [`recording.became_available`](/webhooks/recording-events#recordingbecame_available)
> provides it. After
> [`recording.ended`](/webhooks/recording-events#recordingended), let the source
> stream reach EOF, then complete the multipart upload. Because most of the audio
> is already in S3, you can start playback without first transferring the entire
> recording after it ends.
>
> Use `media_url` for this workflow. The `live_url` starts at the live edge and
> does not include audio recorded before you connect.

See [Recording lifecycle events](/api/recording#lifecycle-events) for complete payload schemas and examples.

Reference: https://docs.vorbal.dev/api/recording/create

## Authentication

- `Authorization` header (bearer token, required) — Application auth. Send `Authorization: Bearer <app_uuid>:<api_key>` using any active key in the app's collection. See [Authentication](https://voice-platform.docs.buildwithfern.com/api/authentication) for details.

## Request

### Path parameters

- `uuid` (string, required) — [Session](/api/sessions) identifier.

### Headers

- `Idempotency-Key` (string, optional) — Optional key for safely retrying mutating requests. See [Idempotency](https://voice-platform.docs.buildwithfern.com/idempotency) for details.
- `Operation-Id` (string, optional) — optional client-supplied lowercase RFC-4122 v4 UUID returned as operation_uuid; platform generates one when omitted; correlation metadata distinct from Idempotency-Key.

### Body (application/json)

- `channels` (enum, required) — Output channel layout. Phase 1 supports `mono`.
  - Allowed values: `mono`, `stereo`
- `direction` (enum, required) — Audio direction relative to the Session. Phase 1 supports `both`.
  - Allowed values: `both`, `session_in`, `session_out`
- `enable_voice_activity_events` (boolean, optional) — When true, emit `recording.speech.started` and `recording.speech.ended` while this session recording is active. Detection is passive and does not alter recorded audio. Omit this field or set it to `false` to disable these events. Not supported for `type: ws` sessions or room recordings.
- `include_gaps` (boolean, optional) — Controls whether redacted periods and periods without recordable audio are included in the recording's duration. Omit this field or set it to `false` to remove those gaps from the artifact. Set it to `true` to include the gaps as silence.
- `start_only_after_answer` (boolean, optional) — When `true` on an inbound or outbound phone Session that has not answered, durably admit the request and wait for the canonical answer before starting. Omit this field or set it to `false` to start as soon as the current media path permits, including available early media. Already-answered and `type: ws` Sessions keep their immediate behavior.
- `voice_activity_config` (object, optional) — Required tuning when voice activity events are enabled. Supplying this object requires `enable_voice_activity_events: true`; otherwise the request is rejected.
  - `sensitivity` (integer, required) — Detection sensitivity. Lower values are more resistant to background noise but can miss quiet speech. Higher values detect quieter or more distant speech but increase the chance that background noise is classified as speech. Start around 25 for noisy environments, 50 for ordinary calls, or 75 for quiet or distant speakers.
  - `silence_duration_ms` (integer, required) — Continuous silence, in milliseconds, before `recording.speech.ended`. Lower values end faster but can split natural pauses; higher values preserve pauses but delay the end event. Durations are evaluated at media-frame granularity, so transitions can take roughly one additional frame plus webhook delivery.
  - `speech_duration_ms` (integer, required) — Minimum continuous speech, in milliseconds, before `recording.speech.started`. Lower values react faster but admit short noises; higher values reject transient sounds but delay the start event.

## Response

### 202

Recording accepted

- `operation_uuid` (string, required)
- `recording_uuid` (string, required)
- `status` (enum, required)
  - Allowed values: `in_progress`

## Examples

**Request**

```json
{
  "channels": "mono",
  "direction": "both"
}
```

**Response**

```json
{
  "operation_uuid": "550e8400-e29b-41d4-a716-446655440000",
  "recording_uuid": "rec_11111111-2222-4333-8444-555555555555",
  "status": "in_progress"
}
```

**SDK Code**

```python Recording_startRecording_example
import requests

url = "https://api.telekesher.dev/v1/sessions/uuid/recordings"

payload = {
    "channels": "mono",
    "direction": "both"
}
headers = {
    "Authorization": "Bearer <token>",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())
```

```javascript Recording_startRecording_example
const url = 'https://api.telekesher.dev/v1/sessions/uuid/recordings';
const options = {
  method: 'POST',
  headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
  body: '{"channels":"mono","direction":"both"}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go Recording_startRecording_example
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api.telekesher.dev/v1/sessions/uuid/recordings"

	payload := strings.NewReader("{\n  \"channels\": \"mono\",\n  \"direction\": \"both\"\n}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("Authorization", "Bearer <token>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby Recording_startRecording_example
require 'uri'
require 'net/http'

url = URI("https://api.telekesher.dev/v1/sessions/uuid/recordings")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"channels\": \"mono\",\n  \"direction\": \"both\"\n}"

response = http.request(request)
puts response.read_body
```

```java Recording_startRecording_example
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.telekesher.dev/v1/sessions/uuid/recordings")
  .header("Authorization", "Bearer <token>")
  .header("Content-Type", "application/json")
  .body("{\n  \"channels\": \"mono\",\n  \"direction\": \"both\"\n}")
  .asString();
```

```php Recording_startRecording_example
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.telekesher.dev/v1/sessions/uuid/recordings', [
  'body' => '{
  "channels": "mono",
  "direction": "both"
}',
  'headers' => [
    'Authorization' => 'Bearer <token>',
    'Content-Type' => 'application/json',
  ],
]);

echo $response->getBody();
```

```csharp Recording_startRecording_example
using RestSharp;

var client = new RestClient("https://api.telekesher.dev/v1/sessions/uuid/recordings");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer <token>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"channels\": \"mono\",\n  \"direction\": \"both\"\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift Recording_startRecording_example
import Foundation

let headers = [
  "Authorization": "Bearer <token>",
  "Content-Type": "application/json"
]
let parameters = [
  "channels": "mono",
  "direction": "both"
] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.telekesher.dev/v1/sessions/uuid/recordings")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```