> ## Documentation Index
> Fetch the complete documentation index at: https://docs.daily.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Storing Daily call recordings in a custom OCI bucket

> Configure Daily to store call recordings and transcripts directly in your own Oracle Cloud Infrastructure Object Storage bucket.

Developers can configure Daily to store call recordings and transcripts in an [Oracle Cloud Infrastructure (OCI) Object Storage](https://www.oracle.com/cloud/storage/object-storage/) bucket of their choice. As with [custom S3 storage](/docs/guides/features/recording/custom-s3-storage), Daily does not store the recording on its own servers at any point — files are written directly into the specified bucket.

<Warning>
  **OCI storage is not self-serve.** Unlike an S3 bucket, you cannot finish this setup on your own through the REST API. Daily has to provision credentials on its side before your bucket will accept writes, and you need an identifier from Daily before you can write your access policy.

  To get started, email [support@daily.co](mailto:support@daily.co) with the details in [step 1](#step-1-send-daily-your-bucket-details) below.
</Warning>

<Note>
  If you're new to call recording with Daily, check out the [recording guide](/docs/guides/features/recording) first.
</Note>

***

## Why OCI setup differs from S3

With S3, Daily calls AWS STS `AssumeRole` against a role you create, using your Daily domain as the external ID. AWS issues Daily short-lived credentials scoped to that role at the moment a recording starts. Everything Daily needs arrives in the API request, so you can complete the whole setup yourself.

OCI has no equivalent. Cross-tenancy access in OCI is granted through [`Endorse` and `Admit` policy statements](https://docs.oracle.com/en-us/iaas/Content/Object/Concepts/accessingresourcesacrosstenancies.htm), which *authorize* an existing principal rather than issuing a new credential for it — Oracle's documentation is explicit that the caller uses "the credentials as usual from the endorsing (source) tenancy." On top of that, Daily writes objects through OCI's [Amazon S3 Compatibility API](https://docs.oracle.com/en-us/iaas/Content/Object/Tasks/s3compatibleapi.htm), whose only supported authentication is a [Customer Secret Key](https://docs.oracle.com/en-us/iaas/Content/Identity/access/working-with-customer-secret-keys.htm) — a static access key and secret key pair, which per Oracle "don't expire." There is no session-token header, so even a short-lived credential could not be used on this path.

The practical consequence is that Daily provisions and holds the credentials on its side, per domain. Oracle also requires that "both tenancies must agree on access": Daily writes its half of the policy inside Daily's tenancy, and you write yours. That exchange is what makes this a support conversation rather than an API call.

***

## Setup process

### Step 1: Send Daily your bucket details

Email [support@daily.co](mailto:support@daily.co) with:

| What              | Example                  | Where to find it                                                          |
| ----------------- | ------------------------ | ------------------------------------------------------------------------- |
| Daily domain name | `your-domain`            | The subdomain of your room URLs, `https://your-domain.daily.co/room-name` |
| OCI namespace     | `axaxnpcrorw5`           | OCI Console → **Tenancy details** → *Object Storage namespace*            |
| Bucket name       | `my-daily-recordings`    | OCI Console → **Storage** → *Buckets*                                     |
| Bucket region     | `us-ashburn-1`           | The region the bucket lives in                                            |
| Compartment       | `recordings-compartment` | The compartment containing the bucket                                     |

### Step 2: Daily provisions its side

Daily creates the principal that will write to your bucket and stores its credentials against your domain. Nothing is required from you during this step.

### Step 3: Apply the policy Daily sends back

Support replies with a suggested policy for your tenancy. It looks like this:

```
Define tenancy DailyCo as ocid1.tenancy.oc1..EXAMPLE-DAILY-TENANCY-OCID
Define group DailyByobRecordings as ocid1.group.oc1..EXAMPLE-DAILY-GROUP-OCID
Admit group DailyByobRecordings of tenancy DailyCo to manage object-family in compartment CUSTOMER-COMPARTMENT where all { target.bucket.name = 'BUCKET-NAME', request.region = 'BUCKET-REGION', request.principal.id = 'ocid1.user.oc1..EXAMPLE-DAILY-USER-OCID', any { request.permission = 'OBJECT_INSPECT', request.permission = 'OBJECT_READ', request.permission = 'OBJECT_CREATE', request.permission = 'OBJECT_OVERWRITE', request.permission = 'OBJECT_DELETE', request.permission = 'BUCKET_READ' } }
```

<Note>
  The OCIDs above are placeholders — support sends you the real ones, which are specific to your domain. Replace `CUSTOMER-COMPARTMENT`, `BUCKET-NAME`, and `BUCKET-REGION` with your own values.

  This policy is a **suggestion**, not a required form. It is written to be tightly scoped: access is limited to a single bucket, in a single region, for a single Daily principal. Depending on how your tenancy is organized you may prefer to express the same grant differently — a different compartment structure, or broader or narrower conditions. What matters is that the Daily group named by support ends up with the listed permissions on your bucket.
</Note>

### Step 4: Wait for the policy to propagate

OCI IAM changes are made in your tenancy's home region and replicated outward. Oracle notes that this can take [several minutes](https://docs.oracle.com/en-us/iaas/Content/Identity/regions/managingregions.htm) to reach all regions. If your bucket is not in your home region, allow **5–10 minutes** before testing — a policy that has not finished propagating fails exactly like a policy that is wrong.

### Step 5: Configure the property and test it

[Set the bucket property](#configuring-your-daily-domain-or-room) on your domain or room, then verify it with the [`test-oci-bucket`](#verifying-your-setup) endpoint.

***

## Configuring your Daily domain or room

OCI storage can be configured at the room or domain level, and applies to both recordings (`recordings_bucket`) and transcripts (`transcription_bucket`). Which configuration gets used depends on the following priority order (highest to lowest):

1. Customer bucket at the room level
2. Customer bucket at the domain level
3. Daily's default storage (no configuration needed)

<Warning>
  `bucket_region` must be an **OCI** region such as `us-ashburn-1`, not an AWS region. The value is used to build the OCI storage endpoint hostname, so an AWS-style region like `us-east-1` produces a connection failure rather than a permissions error.
</Warning>

**Set a custom OCI bucket for recordings at the domain level:**

```bash theme={null}
curl --request POST \
  --url https://api.daily.co/v1/ \
  --header 'Authorization: Bearer $DAILY_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
    "properties": {
      "recordings_bucket": {
        "storage_provider": "oci",
        "namespace": "axaxnpcrorw5",
        "bucket_name": "my-daily-recordings",
        "bucket_region": "us-ashburn-1",
        "allow_api_access": true
      }
    }
  }'
```

**Set a custom OCI bucket for transcripts at the domain level:**

```bash theme={null}
curl --request POST \
  --url https://api.daily.co/v1/ \
  --header 'Authorization: Bearer $DAILY_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
    "properties": {
      "transcription_bucket": {
        "storage_provider": "oci",
        "namespace": "axaxnpcrorw5",
        "bucket_name": "my-daily-transcripts",
        "bucket_region": "us-ashburn-1",
        "allow_api_access": true
      }
    }
  }'
```

**Set a custom OCI bucket at the room level:**

```bash theme={null}
curl --request POST \
  --url https://api.daily.co/v1/rooms/my-room \
  --header 'Authorization: Bearer $DAILY_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
    "properties": {
      "recordings_bucket": {
        "storage_provider": "oci",
        "namespace": "axaxnpcrorw5",
        "bucket_name": "my-daily-recordings",
        "bucket_region": "us-ashburn-1",
        "allow_api_access": true
      }
    }
  }'
```

**Revert to Daily default storage:**

```bash theme={null}
curl --request POST \
  --url https://api.daily.co/v1/rooms/my-room \
  --header 'Authorization: Bearer $DAILY_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{"properties": {"recordings_bucket": null}}'
```

### Properties

| Property                      | Required | Notes                                                                                                                      |
| ----------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------- |
| `storage_provider`            | Yes      | Must be `"oci"`. Omitting it means the config is read as an AWS S3 bucket.                                                 |
| `namespace`                   | Yes      | Your OCI Object Storage namespace. OCI only — not accepted for S3 buckets.                                                 |
| `bucket_name`                 | Yes      | Name of your OCI bucket. Cannot contain `/`.                                                                               |
| `bucket_region`               | Yes      | OCI region of your bucket, e.g. `us-ashburn-1`.                                                                            |
| `allow_api_access`            | Yes      | Whether Daily's access-link APIs may hand out download links.                                                              |
| `allow_streaming_from_bucket` | No       | Whether download links open in the browser (`inline`) rather than saving to disk (`attachment`). `recordings_bucket` only. |

<Note>
  `assume_role_arn` is an S3-only property and is **rejected** for OCI buckets. Likewise `namespace` is rejected for S3 buckets. Sending the wrong provider's fields returns a `400`.
</Note>

***

## What each permission is for

The policy Daily sends you grants a set of `object-family` permissions. The main ones map to Daily's work like this:

| Permission         | What Daily uses it for                                                                                                                                            |
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `OBJECT_CREATE`    | Writes recording and transcript files into your bucket, including the one-time `daily-co-test-upload.txt` file used to verify your setup.                         |
| `OBJECT_OVERWRITE` | Replaces an object at a key that already exists. An in-progress transcript is rewritten every two minutes, and re-running the bucket test replaces the test file. |
| `OBJECT_INSPECT`   | Lists objects and reads object metadata — used to enumerate the files belonging to a recording and to report their sizes.                                         |
| `OBJECT_READ`      | Reads a finished file back out of your bucket. This is what download and playback links serve.                                                                    |

`BUCKET_READ` and `OBJECT_DELETE` are granted alongside these. They cover bucket-level metadata lookups and cleaning up uploads that could not be completed.

### Optional: restricting read access

If your security policy says Daily should never read your recordings back, you can narrow the `request.permission` list in the `Admit` statement to the write-side permissions only. Ask support which permissions are safe to drop for your configuration — completing an interrupted multipart upload needs more than `OBJECT_CREATE` alone, so trimming the list by guesswork can leave recordings stranded.

Here is what stops working once Daily cannot read the bucket:

* **Download and playback links from Daily.** The [recording access link](/reference/rest-api/recordings/get-recording-link) API and the recording player in the Daily dashboard both sign a link with Daily's credentials. Without read access that link fails when someone opens it. Set `allow_api_access` to `false` so your team is not handed links that cannot work.
* **The MP4 seek atom.** Daily can add a seek atom ("faststart") to finished MP4 files, which is what lets a player jump to the middle of a video without downloading all of it. That step reads the file back, so it cannot run.
* **[Batch processor](/reference/rest-api/batch-processor/index) jobs.** Anything that processes a finished recording has to read it first.

The recording files themselves are complete and valid either way. You can add the seek atom yourself after downloading:

```bash theme={null}
ffmpeg -i input.mp4 -c copy -movflags +faststart output.mp4
```

<Warning>
  Pair a read-restricted policy with `allow_api_access: false`. The bucket test generates a download link whenever `allow_api_access` is `true`, so a policy without read access combined with `allow_api_access: true` fails the test with `upload succeeded but access-link generation failed` — even though writes are working correctly.
</Warning>

***

## Verifying your setup

Unlike S3 buckets, **an OCI bucket configuration is not checked when you set it.** Setting the property succeeds even if the policy is wrong or has not finished propagating, because Daily cannot usefully test the bucket at that moment. Use the `test-oci-bucket` endpoint to confirm the setup works before you rely on it.

```bash theme={null}
curl --request GET \
  --url 'https://api.daily.co/v1/recordings/test-oci-bucket' \
  --header 'Authorization: Bearer $DAILY_API_KEY'
```

**Query parameters:**

* `property` — which property to test: `recordings_bucket` (default) or `transcription_bucket`.
* `roomName` — test the room-level property on that room. Omit it to test the domain-level property. There is no fallback from room to domain; if the room does not set the property, the call returns a `400`.

**Example: test the transcription bucket**

```bash theme={null}
curl --request GET \
  --url 'https://api.daily.co/v1/recordings/test-oci-bucket?property=transcription_bucket' \
  --header 'Authorization: Bearer $DAILY_API_KEY'
```

A successful response uploads `daily-co-test-upload.txt` to your bucket and returns:

```json theme={null}
{
  "success": true,
  "recordings_bucket": {
    "storage_provider": "oci",
    "namespace": "axaxnpcrorw5",
    "bucket_region": "eu-frankfurt-1",
    "bucket_name": "my-daily-recordings",
    "allow_api_access": true,
    "allow_streaming_from_bucket": false
  },
  "test_file_key": "daily-co-test-upload.txt",
  "download_link": "https://axaxnpcrorw5.compat.objectstorage.eu-frankfurt-1.oraclecloud.com/my-daily-recordings/daily-co-test-upload.txt?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=<redacted>%2F20260820%2Feu-frankfurt-1%2Fs3%2Faws4_request&X-Amz-Date=20260820T125850Z&X-Amz-Expires=3600&X-Amz-Signature=<redacted>&X-Amz-SignedHeaders=host",
  "expires": 1787234330
}
```

The echoed configuration includes any defaults that were applied when the property was set — `allow_streaming_from_bucket` above was not sent by the caller. The download link is a standard AWS Signature Version 4 presigned URL against the OCI S3 compatibility endpoint; the credential and signature values are redacted here.

`download_link` and `expires` are only returned when `allow_api_access` is `true`; the link is valid for one hour. Otherwise `download_link` explains that no link was generated.

### Troubleshooting

| Response                                             | What it usually means                                                                                                                                                                    |
| ---------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `credentials not set, contact support@daily.co`      | Daily has not provisioned credentials for your domain yet. Step 2 is incomplete.                                                                                                         |
| `upload failed (...)`                                | The bucket is reachable but the write was refused, or the bucket details are wrong. Check `bucket_name`, `bucket_region` and `namespace`, and confirm the `Admit` policy has propagated. |
| `upload succeeded but access-link generation failed` | Writes work but Daily cannot read the object back. Either grant read access or set `allow_api_access` to `false`.                                                                        |
| `... is not an OCI bucket`                           | The property is set but its `storage_provider` is not `"oci"`. This endpoint only tests OCI buckets.                                                                                     |
| `... property is not set on ...`                     | No bucket property on that scope. If you passed `roomName`, note that there is no fallback to the domain-level property.                                                                 |

If a test fails immediately after applying the policy, wait a few minutes and try again before changing anything — see [step 4](#step-4-wait-for-the-policy-to-propagate).

If you have any questions or run into issues with the setup, [contact us](https://www.daily.co/contact/support).
