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

# Video room recording to your own bucket (BYO storage)

> Deliver video room recordings to your own S3 or GCS bucket — or pin them to a declared region on Orbit-managed storage — with the recording_storage field on room create and recording start.

# Video room recording to your own bucket (BYO storage)

By default, a recorded video room lands on Orbit-managed storage. Regulated and
enterprise tenants often need more than that default: the recording must land
in a bucket the tenant owns, or at least be pinned to a declared storage
region for a data-residency obligation.

The **`recording_storage`** field gives you that control. You can set it per
room (on create) and per recording (on start), and its `provider` discipline
decides where the egress writes:

* **`orbit`** — Orbit-managed storage (the default when you omit the field;
  there is no behavioural change for existing rooms). You may add a `region`
  pin that is recorded for audit.
* **`s3`** — your own AWS S3 bucket, or an S3-compatible store (Cloudflare R2,
  Wasabi, MinIO, Backblaze B2).
* **`gcs`** — your own Google Cloud Storage bucket.

You can set the destination on **room create** (`POST
/video/rooms-scheduled`) so it applies to the auto-start recording, or pass it
on **recording start** (`POST /video/rooms-scheduled/:id/recording/start`) for
a manual start. Omit it and nothing changes — the recording goes to
Orbit-managed storage.

## How secrets are handled

A BYO destination needs credentials, and the shape of those credentials
depends on the provider. The rule is the same on every path: the egress
consumes the secret when the recording starts and **never persists it**. Only
the non-secret descriptor — provider, bucket, region, prefix, endpoint —
reaches the audit log and the API response. This mirrors the RTMP egress
rule (audit the URL, never the stream key).

Because the credentials travel in the request body, use a key you can rotate
freely, and prefer a dedicated bucket-scoped credential rather than an
account-wide one.

## Configure an S3 destination

Pass `provider: "s3"` with the bucket, region, and a key pair:

```bash theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/video/rooms-scheduled" \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Quarterly compliance review",
    "recording_enabled": true,
    "recording_storage": {
      "provider": "s3",
      "bucket": "acme-room-recordings",
      "region": "eu-west-1",
      "access_key_id": "AKIA...",
      "secret_access_key": "...",
      "prefix": "rooms/2026"
    }
  }'
```

* **`bucket`** — 3–255 chars. Must be a valid S3/GCS-style bucket name
  (lowercase alphanumerics, `.`, `_`; no uppercase).
* **`region`** — required for S3. A cloud region slug such as `eu-west-1`.
* **`prefix`** — optional object-key prefix, e.g. `rooms/2026`. Restricted to
  `[A-Za-z0-9._/-]`.
* **`endpoint`** — only for S3-compatible stores (R2, Wasabi, MinIO, B2). An
  http(s) URL. The egress server dereferences it, so the API rejects any
  endpoint pointing at a private, loopback, or cloud-metadata address (SSRF
  guard).
* **`force_path_style`** — set `true` for most non-AWS-compatible stores.

The credential you send needs, at minimum, `s3:PutObject` on the destination
bucket (and `s3:GetObject` if your flow re-reads the artifact). Restrict the
bucket's IAM policy to the egress use and let bucket lifecycle rules own
retention — Orbit delivers the artifact; expiry policies and versioning stay
with you.

## Configure a GCS destination

Pass `provider: "gcs"` with a bucket and, optionally, a service-account key:

```bash theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/video/rooms-scheduled/:id/recording/start" \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "recording_storage": {
      "provider": "gcs",
      "bucket": "acme-room-recordings",
      "prefix": "rooms",
      "credentials_json": "{\"type\":\"service_account\",...}"
    }
  }'
```

* **`credentials_json`** — optional. When omitted, the recording upload
  authenticates with the platform's workload identity and your bucket grants
  it access (the cross-project grant case). When supplied, it is a complete
  service-account JSON key (2–16,384 chars).

Grant the service account `storage.objects.create` on the destination bucket.
As with S3, bucket lifecycle rules — not Orbit — own retention of the
delivered artifact.

## Pin a region on Orbit-managed storage

If your residency obligation is satisfied by Orbit-managed storage in an EU or
other declared region, you do not need a BYO bucket — pass `provider: "orbit"`
with a `region`:

```json theme={null}
{
  "recording_storage": {
    "provider": "orbit",
    "region": "eu"
  }
}
```

The region is recorded for audit alongside the room. Omitting `region` (or the
whole field) preserves the default behaviour exactly.

## Why the audit log shows only the descriptor

Compliance reviewers answer "where does the recorded file live?" — not "what
was the key." The API response and the audit log therefore carry only the
non-secret descriptor of every storage destination your tenant has used:
provider, bucket, region, prefix, endpoint. The access key, secret key, and
service-account JSON never appear. This gives you an enforcement-grade
record of residency for every room without making the audit trail a secret
store.

## Compliance and residency fit

This control is **tenant-owned**: Orbit delivers to the destination you
declare and never mandates one region or provider over another. Regulated
tenants typically combine it with the room's live-media region pin (the
`region` field on room create) so both the live session and the stored
artifact stay in scope. HIPAA tenants record PHI-in-scope destinations as part
of their processing-records catalog; see the
[HIPAA compliance guide](/compliance/hipaa) for the BAA-gated posture this
destination rolls up under.

Common validation failures at the API edge:

* **400 on create/start** — a malformed bucket name, a missing required field
  (`region` for S3), or an S3 `endpoint` that resolves to a private or
  metadata address. The error names the exact field.
* **Recording fails at egress start** — the credentials went through
  validation but the bucket rejected the upload. Check the IAM policy and
  bucket name; the audit row carries the descriptor to debug against.

## Where to go next

* [Video meetings and conferences](/guides/video-meetings) — room create,
  join, and the recording basics this destination extends.
* [Video room history](/guides/video-room-history) — where the completed
  recording and transcript surface in the dashboard.
* [Video API reference](/api-reference/video) — the full scheduled-room
  endpoint surface.
