MeetStream Guide: Custom S3 Storage

View as Markdown

By default, MeetStream stores all bot media — audio, video, transcripts, screenshots, chat logs, and meeting manifests — in its own S3 bucket, and serves them to you via presigned URLs when you call the fetch endpoints.

Custom S3 Storage lets you direct MeetStream to upload all of that media directly into your own S3 bucket using credentials you provide.

Use cases:

  • Compliance or data-residency requirements (your data never leaves your AWS account).
  • Direct integration with your own storage pipeline or data lake.
  • Full control over file lifecycle, retention, and access policies.

1) Prerequisites

  1. An AWS S3 bucket in the region of your choice.
  2. An IAM user (or role) with credentials that grant at least write access (s3:PutObject) to that bucket. If you also want MeetStream to serve the files back to you via its API, you’ll need to additionally grant read access (s3:GetObject).
  3. A MeetStream API key.

Minimum IAM policy

Replace your-bucket-name with your bucket name.

1{
2 "Version": "2012-10-17",
3 "Statement": [
4 {
5 "Effect": "Allow",
6 "Action": [
7 "s3:PutObject",
8 "s3:PutObjectAcl"
9 ],
10 "Resource": "arn:aws:s3:::your-bucket-name/meetstream/*"
11 }
12 ]
13}

If you also want the MeetStream API to serve presigned download URLs from your bucket (i.e. access_mode: "read_write"), add s3:GetObject to the same statement.

If you set a custom prefix (see below), scope the Resource to that prefix instead — e.g. arn:aws:s3:::your-bucket-name/recordings/meetstream/*. If you use per-category prefixes, add one Resource entry per distinct prefix (e.g. .../recordings/audio/*, .../recordings/video/*, …).


2) Configure your S3 bucket

1PUT /api/v1/admin/configs?config_type=storage
2Authorization: Token <YOUR_API_KEY>
3Content-Type: application/json
1{
2 "provider": "aws",
3 "bucket_name": "your-bucket-name",
4 "region": "us-east-1",
5 "access_key_id": "AKIA...",
6 "secret_key": "...",
7 "access_mode": "read_write",
8 "prefix": "recordings/meetstream",
9 "prefixes": {
10 "audio": "recordings/audio",
11 "video": "recordings/video",
12 "transcript": "transcripts",
13 "metadata": "metadata"
14 }
15}

Parameters

ParameterTypeRequiredDescription
providerstringYesStorage provider. Only "aws" is supported.
bucket_namestringYesName of your S3 bucket.
regionstringYesAWS region of the bucket (e.g. "us-east-1").
access_key_idstringYesAWS access key ID for an IAM user with access to the bucket.
secret_keystringYesAWS secret access key.
access_modestringNo"read_write" (default) or "write_only". See Access Modes.
prefixstringNoBase key prefix under which media is stored. Defaults to "meetstream". Acts as the fallback for any artifact category not given its own prefix in prefixes. Leading/trailing slashes are stripped and .. segments are rejected.
prefixesobjectNoPer-artifact-category prefix overrides. Optional keys: audio, video, transcript, metadata. Each lets you store that category under its own top-level path (e.g. audio under recordings/audio/{bot_id}/...). Any category omitted falls back to prefix. Same normalization and .. rejection as prefix. See File layout.
endpoint_urlstringNoCustom S3-compatible endpoint (e.g. for MinIO or Cloudflare R2). Omit for standard AWS S3.

Validation

For read_write, MeetStream performs a live HeadBucket check against your bucket before saving the configuration. If the credentials are invalid or the bucket doesn’t exist, the request will fail with a 400 error.

For write_only, MeetStream validates by writing a tiny probe object under each distinct prefix you configured ({prefix}/.write_probe_*, plus one per unique category prefix). This confirms your credentials can write without requiring read access, and works even if your IAM policy is scoped to those individual prefixes. If any probe write fails, the request returns a 400 error.

Response

1{
2 "message": "Storage config saved successfully",
3 "provider": "aws",
4 "bucket_name": "your-bucket-name",
5 "region": "us-east-1",
6 "access_mode": "read_write",
7 "prefix": "recordings/meetstream",
8 "prefixes": {
9 "audio": "recordings/audio",
10 "video": "recordings/video",
11 "transcript": "transcripts",
12 "metadata": "metadata"
13 }
14}

Your access_key_id and secret_key are stored securely and are never returned in any API response.


3) Access modes

ModeMeetStream uploads to your bucketMeetStream API serves files from your bucket
read_write (default)YesYes — fetch endpoints return presigned URLs backed by your bucket
write_onlyYesNo — fetch endpoints return 403

Use write_only when:

  • Your bucket is private and you want to control access yourself (generate your own presigned URLs).
  • Your IAM policy only grants write permissions.
  • You are piping files into your own processing pipeline and don’t need MeetStream to serve them.

If you start with write_only and later upgrade to read_write, you can immediately use the fetch endpoints — no re-upload needed.


4) File layout in your bucket

MeetStream does not create a per-bot subfolder. Files are written directly into your configured prefix folder, with the bot id as a filename prefix ({prefix}/{bot_id}_<file>). This keeps each bot’s objects grouped by name while letting many bots share one folder. With the default prefix:

meetstream/
abc123_audio.wav
abc123_meeting_recording.mp4
abc123_participants.json
abc123_chats.json
abc123_manifest.json
abc123_screenshots/
9f2b….png
9f2b….json
a1c3….png
a1c3….json
def456_audio.wav ← a second bot, same folder
def456_manifest.json

Or, with "prefix": "recordings/meetstream", the same files live under recordings/meetstream/abc123_....

Multi-file collections (screenshots/, and transcript JSON under transcription/<provider>/<id>/) keep a {bot_id}_<collection>/ subfolder so their many files stay grouped — there is still no bare {bot_id}/ folder.

Per-category prefixes

Each file belongs to one of four artifact categories, and you can route each category to its own top-level prefix via the prefixes object. Any category you don’t set inherits the base prefix.

CategoryFilesConfigured by
audioaudio.wav, audio.mp3prefixes.audio
videomeeting_recording.mp4prefixes.video
transcripttranscription/.../raw_transcript.json, processed_transcript.jsonprefixes.transcript
metadatamanifest.json, participants.json, chats.json, screenshots/prefixes.metadata

For example, with "prefixes": {"audio": "recordings/audio", "video": "recordings/video", "transcript": "transcripts", "metadata": "metadata"}:

recordings/audio/abc123_audio.wav
recordings/video/abc123_meeting_recording.mp4
transcripts/abc123_transcription/deepgram/<id>/processed_transcript.json
metadata/abc123_manifest.json
metadata/abc123_screenshots/9f2b….png

Prefixes isolate MeetStream files from any other objects in your bucket. You can change any prefix at any time; media written before a change remains fetchable at its original location (MeetStream records the exact bucket and key prefix used for each bot). New bots use the updated prefixes.


5) Fetching media after configuration

Once custom storage is configured, all subsequent bots write their media to your bucket. The MeetStream fetch endpoints work exactly the same as before — you do not need to change how you call them.

Fetch endpoints and URL behaviour

EndpointBehaviour with read_writeBehaviour with write_only
GET /bots/{bot_id}/get_audioPresigned URL from your bucket (1-hour expiry)403
GET /bots/{bot_id}/get_videoPresigned URL from your bucket (1-hour expiry)403
GET /bots/{bot_id}/screenshotsPaginated list of screenshots with presigned URLs (1-hour expiry). Not supported for Zoom bots.403
GET /bots/{bot_id}/screenshots/{id}Single screenshot by UUID with presigned URL (1-hour expiry). Not supported for Zoom bots.403
GET /bots/{bot_id}?type=chatJSON content read from your bucket403
GET /bots/{bot_id}?type=participantsJSON content read from your bucket403
GET /bots/{bot_id}?type=transcriptionJSON content read from your bucket403
GET /transcript/{transcript_id}/get_transcriptJSON content read from your bucket403

The 403 response for write_only includes a message indicating where the file lives:

1{
2 "message": "Media is stored in your S3 bucket, but only write access was granted. Update your storage configuration to read+write to fetch media via the API.",
3 "access_mode": "write_only"
4}

6) Existing bots are not affected by configuration changes

Each bot’s media is pinned to whichever bucket was active when its files were written (after the bot leaves the meeting). Enabling, disabling, or changing custom storage will not affect media from bots that have already been processed — those files remain in the bucket they were originally written to.

ScenarioWhere the files are
Bot processed before custom storage was enabledPlatform default bucket
Bot processed after custom storage was enabledYour custom bucket
Custom storage disabled after a bot is processedFiles stay in your custom bucket

7) View your current storage configuration

1GET /api/v1/admin/configs
2Authorization: Token <YOUR_API_KEY>

Returns your current storage configuration. Credential fields are never included in the response.

1{
2 "StorageConfig": {
3 "aws": {
4 "active": true,
5 "bucket_name": "your-bucket-name",
6 "region": "us-east-1",
7 "access_mode": "read_write",
8 "prefix": "recordings/meetstream",
9 "prefixes": {
10 "audio": "recordings/audio",
11 "video": "recordings/video",
12 "transcript": "transcripts",
13 "metadata": "metadata"
14 },
15 "configured_at": "2025-01-15T10:00:00Z",
16 "last_validated_at": "2025-01-15T10:00:00Z"
17 }
18 }
19}

8) Delete your storage configuration

1DELETE /api/v1/admin/configs?key_name=aws
2Authorization: Token <YOUR_API_KEY>

This removes your credentials and storage configuration. After deletion, new bots will have their media written to the MeetStream platform bucket.

Existing bots whose files were already written to your bucket are not affected — those files are not deleted from your bucket.


9) Troubleshooting

Configuration save fails with 400

  • Confirm the bucket name and region are correct.
  • Confirm the IAM credentials have s3:PutObject and s3:GetObject (for read_write) on the bucket.
  • Confirm access_mode is "read_write" if you want live validation.

Fetch endpoint returns 403 with "access_mode": "write_only"

  • Your storage is configured as write_only. Either access the file directly from your bucket, or update your configuration to read_write.

Fetch endpoint returns 404 for a bot that was processed

  • The file may not have been written to your bucket due to a permissions error during upload. Check that your IAM credentials are valid and have s3:PutObject on arn:aws:s3:::your-bucket-name/meetstream/* (or your configured prefix, e.g. arn:aws:s3:::your-bucket-name/recordings/meetstream/*).

Presigned URL returns 403 Forbidden

  • The URL may have expired — presigned URLs have a short lifetime (see the table in Section 5). Call the fetch endpoint again to get fresh URLs.
  • Your IAM policy may not include s3:GetObject. Update the policy and your storage configuration.

Bot was processed before I enabled custom storage — files are not in my bucket

  • This is expected. Only bots processed after the configuration was saved write to your bucket. See Section 6.