Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.tavus.io/llms.txt

Use this file to discover all available pages before exploring further.

The recording_storage config field works for Amazon S3, Google Cloud Storage, and Azure Blob Storage — pick a provider, configure a one-time trust relationship on your side, and pass us the resulting non-secret identifiers.
No customer secrets are stored at Tavus. Every supported path uses provider-native federated identity (IAM role assumption, GCP Workload Identity Federation, or Entra ID Federated Credentials). You configure trust on your side; we receive short-lived tokens at runtime.
Recordings are typically available in your bucket within seconds to a few minutes after the call ends, depending on call length and provider. Once the recording lands, Tavus fires application.recording_ready (with storage_provider and a fully-qualified storage_uri) to your callback_url. See Webhooks and Callbacks.

Set up your storage

S3 is the fastest path — recordings are written directly into your bucket as they finalize. Works in every AWS region.
1

Create an IAM role in your AWS account

Configure the role’s trust relationship with all three of the following — every field is mandatory:
  • Trusted AWS principal: AWS account ID 291871421005.
  • ExternalId: tavus.
  • Max session duration: 12 hours (43200 seconds). AWS roles default to 1 hour, but the recording service requests 12-hour sessions when assuming the role. A role with the default duration will fail validation at room creation with unable to assume role with given parameters.
About the trusted AWS account. Tavus’s recording infrastructure is operated through Daily.co; AWS account ID 291871421005 belongs to them. The same account ID is documented in Daily’s S3 setup guide for customers running their own security review. The ExternalId tavus is Tavus’s identifier with Daily, gating cross-account sts:AssumeRole per the confused-deputy AWS pattern.
Permissions policy (scoped to your bucket):
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": [
      "s3:PutObject",
      "s3:GetObject",
      "s3:ListBucketMultipartUploads",
      "s3:AbortMultipartUpload",
      "s3:ListBucketVersions",
      "s3:ListBucket",
      "s3:GetObjectVersion",
      "s3:ListMultipartUploadParts"
    ],
    "Resource": [
      "arn:aws:s3:::your-bucket-name",
      "arn:aws:s3:::your-bucket-name/*"
    ]
  }]
}
2

Pass the role on conversation creation

cURL
curl --request POST \
  --url https://tavusapi.com/v2/conversations \
  --header 'Content-Type: application/json' \
  --header 'x-api-key: <api_key>' \
  --data '{
    "properties": {
      "recording_storage": {
        "provider": "s3",
        "bucket_name": "your-bucket-name",
        "bucket_region": "us-east-1",
        "assume_role_arn": "arn:aws:iam::123456789012:role/TavusRecordingWriter"
      }
    },
    "replica_id": "r5f0577fc829"
  }'
The original setup using flat properties on properties (without the recording_storage object) continues to work. Existing integrations don’t need to change.
cURL
curl --request POST \
  --url https://tavusapi.com/v2/conversations \
  --header 'Content-Type: application/json' \
  --header 'x-api-key: <api_key>' \
  --data '{
    "properties": {
      "enable_recording": true,
      "recording_s3_bucket_name": "your-bucket-name",
      "recording_s3_bucket_region": "us-east-1",
      "aws_assume_role_arn": "arn:aws:iam::123456789012:role/TavusRecordingWriter"
    },
    "replica_id": "r5f0577fc829"
  }'
These map internally to provider: "s3". New integrations should use recording_storage — it’s the only way to access GCS, Azure, and unsupported S3 regions, and it’s where new fields will be added.
resource "aws_s3_bucket" "recordings" {
  bucket = "your-recording-bucket"
}

resource "aws_iam_role" "tavus_writer" {
  name = "TavusRecordingWriter"

  # The recording service requests 12-hour sessions; default 3600s will fail.
  max_session_duration = 43200

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect    = "Allow"
      Principal = { AWS = "arn:aws:iam::291871421005:root" }
      Action    = "sts:AssumeRole"
      Condition = {
        StringEquals = { "sts:ExternalId" = "tavus" }
      }
    }]
  })
}

resource "aws_iam_role_policy" "writer" {
  name = "TavusRecordingWriter-s3-write"
  role = aws_iam_role.tavus_writer.id
  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect = "Allow"
      Action = [
        "s3:PutObject",
        "s3:GetObject",
        "s3:ListBucketMultipartUploads",
        "s3:AbortMultipartUpload",
        "s3:ListBucketVersions",
        "s3:ListBucket",
        "s3:GetObjectVersion",
        "s3:ListMultipartUploadParts",
      ]
      Resource = [
        aws_s3_bucket.recordings.arn,
        "${aws_s3_bucket.recordings.arn}/*",
      ]
    }]
  })
}

Start recording

Recording does not start automatically — you need to trigger it from your frontend after the participant joins:
const call = Daily.createCallObject();

call.on('joined-meeting', () => {
  call.startRecording();
});

Receive the recording

Once the recording lands in your destination, Tavus fires application.recording_ready to your callback_url:
{
  "properties": {
    "bucket_name": "<your-bucket>",
    "s3_key": "<object-key>",
    "duration": 1234,
    "storage_provider": "gcs",
    "storage_uri": "gs://<your-bucket>/<object-key>"
  },
  "conversation_id": "<conversation_id>",
  "event_type": "application.recording_ready",
  "message_type": "application",
  "timestamp": "2026-04-30T22:11:14Z"
}
The s3_key / storage_uri object key follows the pattern <conversation_id>/<file>.mp4. The conversation UUID prefix is stable; the filename is assigned by the recording service. Recordings are MP4 files. For GCS and Azure Blob, if delivery to your bucket exhausts retries (typically due to a misconfigured trust policy on your side), Tavus instead fires application.recording_copy_failed with error_code and error_message. The recording is retained in Tavus’s recording infrastructure for ~30 days as a manual recovery window. See event reference.

Verify your setup

After your first recording, check:
  1. application.recording_ready arrives at your callback URL — typically within ~1 minute for an average call, longer for multi-hour recordings.
  2. The storage_uri resolves — try opening it (or fetching it) from your cloud’s CLI.
  3. If you see application.recording_copy_failed instead, the error_code is your starting point: DESTINATION_AUTH_FAILED is almost always a trust-policy issue (verify the issuer URI, subject claim, or assume-role principal).