How to Export AI Call Recordings to Google Cloud Storage & Amazon S3 (2026)
Export your AI call recordings to your own cloud. Download permanent recording links from Edesy as a CSV, then bulk-upload to Google Cloud Storage (GCS) or Amazon S3 with a copy-paste script.
S
Suvojit Mondal//10 min read
If you run voice campaigns on Edesy, the call recordings belong to you — and there will come a day when you want them living in your own cloud storage rather than only on the platform. Maybe compliance requires a copy in your own Google Cloud bucket, maybe your data team wants to run analytics on the audio, or maybe you're simply building a long-term archive. Whatever the reason, getting your recordings out should be a 15-minute job, not an engineering project.
This guide walks through the complete workflow: export a manifest of permanent recording links from the dashboard, then bulk-upload every recording to Google Cloud Storage (GCS) — with an Amazon S3 variant at the end. No platform-side engineering required; everything here you can do yourself.
Quick verdict (TL;DR)
Step 1 — Export: On the Campaigns page, select your campaigns → Export → tick Recordings and Permanent links → download the CSV.
Step 2 — Read the links: The CSV has a Recording URL column. With "Permanent links" enabled, those URLs never expire, so they're safe to process at your own pace.
Step 3 — Upload: Run the copy-paste Python script below. It streams each recording straight from Edesy into your GCS bucket (no local disk needed) and is safe to re-run.
Amazon S3 user? Same flow — swap one block in the script (shown below).
Cost: Free to export. You only pay your cloud provider for storage (cents per GB/month).
Step 1: Export a manifest of permanent recording links
Everything starts with a CSV that lists every call alongside a link to its recording. You generate this from the dashboard — no API keys, no code.
Sign in to your dashboard at voice-agent.edesy.in.
Open the Campaigns page and select the campaign (or campaigns) whose recordings you want. You can also narrow by date range.
Click Export.
In the export dialog, tick two checkboxes:
Recordings — includes a link to each call's audio.
Permanent links — upgrades those links from short-lived (7-day) URLs to non-expiring ones you can safely store and process on your own systems.
Click export. A file like campaigns-export-2026-06-29.csv downloads to your machine.
Why "Permanent links" matters: Without it, the export uses presigned URLs that expire after 7 days — fine for a quick integration, but if your upload job stalls or you process the file weeks later, the links would be dead. The permanent option uses signed tokens that keep working as long as the recording exists, so a large migration can't fail halfway through link rot.
If the calls you want aren't part of a campaign (for example, inbound calls), the same export option is available per agent: open an agent → Call Logs → Export, with the identical Recordings + Permanent links checkboxes.
Step 2: Understand the CSV
Open the CSV and you'll see one row per call. For this task, the column that matters is Recording URL. Each value looks like:
They stream the audio directly. Opening one in a browser plays (or downloads) the recording.
They carry no file extension. The format (.mp3, .wav, or .ogg) is determined by the call, not the URL. Our script handles this automatically.
Add &download=true to any link and the server responds with a proper filename — recording-<conversation-id>.mp3 (or .wav/.ogg). That's the trick the script uses to preserve the right extension.
Other useful columns in the same row include Call SID, Phone Number, Started At, Duration (seconds), and Disposition — handy if you want to name files by phone number or organise them by date.
Step 3: Upload every recording to Google Cloud Storage
Here's a self-contained Python script. It reads the CSV, streams each recording straight from Edesy into your GCS bucket (nothing is written to local disk), preserves the correct audio extension, and skips files already uploaded so you can safely re-run it.
Prerequisites
# 1. Install the two librariespip install requests google-cloud-storage# 2. Authenticate to Google Cloud (one-time, opens a browser)gcloud auth application-default login# 3. Make sure your target bucket existsgcloud storage buckets create gs://my-call-recordings --location=asia-south1
The script (export_to_gcs.py)
import csvimport reimport sysimport requestsfrom google.cloud import storage# ---- Configure these three values ----CSV_PATH = "campaigns-export-2026-06-29.csv" # the file you downloadedGCS_BUCKET = "my-call-recordings" # your bucket nameGCS_PREFIX = "edesy-recordings/" # folder inside the bucket# --------------------------------------client = storage.Client() # uses your `gcloud auth` credentialsbucket = client.bucket(GCS_BUCKET)# Map server content types to extensions as a fallbackCONTENT_TYPE_EXT = { "audio/mpeg": "mp3", "audio/mp3": "mp3", "audio/wav": "wav", "audio/x-wav": "wav", "audio/ogg": "ogg",}def filename_for(resp): """Recover the real filename (with extension) from the response.""" cd = resp.headers.get("Content-Disposition", "") match = re.search(r'filename="?([^"]+)"?', cd) if match: return match.group(1) # Fallback: derive extension from the content type ext = CONTENT_TYPE_EXT.get(resp.headers.get("Content-Type", "").split(";")[0], "mp3") return f"recording.{ext}"uploaded = skipped = failed = 0# utf-8-sig strips the BOM the export adds for Excel compatibilitywith open(CSV_PATH, newline="", encoding="utf-8-sig") as f: for row in csv.DictReader(f): url = (row.get("Recording URL") or "").strip() if not url: continue # Ask the server for a download response with a proper filename dl_url = url + ("&download=true" if "?" in url else "?download=true") try: resp = requests.get(dl_url, stream=True, timeout=180) except requests.RequestException as e: print(f"FAILED {row.get('Call SID', '?')}: {e}") failed += 1 continue if resp.status_code != 200: # 404 usually means the recording isn't ready/available for that call print(f"SKIP {row.get('Call SID', '?')}: HTTP {resp.status_code}") skipped += 1 continue blob_name = GCS_PREFIX + filename_for(resp) blob = bucket.blob(blob_name) if blob.exists(): # idempotent: never re-upload skipped += 1 continue blob.upload_from_file( resp.raw, content_type=resp.headers.get("Content-Type", "application/octet-stream"), ) uploaded += 1 print(f"OK gs://{GCS_BUCKET}/{blob_name}")print(f"\nDone. uploaded={uploaded} skipped={skipped} failed={failed}")
Run it
python export_to_gcs.py
You'll see one line per recording as it lands in your bucket, then a summary. Because the script checks blob.exists() before uploading, you can interrupt it and re-run it any time — it picks up exactly where it left off.
Amazon S3 instead? Change one block
If your destination is AWS rather than Google Cloud, the only thing that changes is the upload mechanism. Install boto3, make sure your AWS credentials are configured (aws configure), and swap the GCS client and upload call:
Everything else — reading the CSV, the permanent links, the extension handling — stays identical.
Prefer no script? The two-command fallback
If you'd rather not run a streaming uploader, you can do it in two stages: download the recordings to a local folder, then push the whole folder with a single command.
Download each link to a local recordings/ folder (a trimmed version of the script above writing to disk instead of the cloud), then upload the entire folder in one shot:
Both gcloud storage cp and aws s3 sync are resumable and skip files that already exist, so this path is just as safe to re-run as the streaming script.
Verify your migration
Before you consider the job done, run two quick checks:
Count match. Compare the number of rows in your CSV that have a Recording URL against the object count in your bucket:
gcloud storage ls gs://my-call-recordings/edesy-recordings/ | wc -l
(Remember: rows where the recording wasn't available will show as SKIP in the script output — that's expected, not an error.)
Spot-play a file. Download one recording from your bucket and play it to confirm the audio is intact and the format opens cleanly.
Automating ongoing backups
This same workflow scales into a continuous backup. Two common patterns:
Scheduled sync: Put the export + upload script on a nightly cron job with a rolling date filter (for example, "yesterday's calls"). Since the script skips anything already in your bucket, overlap is harmless.
Event-driven: Trigger the upload from a post-call webhook so each recording lands in your bucket shortly after the call ends.
Either way, you end up with a self-maintaining mirror of your call audio in infrastructure you control.
Frequently asked questions
Do the permanent recording links ever expire?
No. The "Permanent links" export option generates non-expiring HMAC-signed URLs that you can store and reuse on your own systems. They are different from the default 7-day links used for short-lived integrations. As long as the recording exists on the Edesy platform, the link keeps working.
What audio format will the recordings be in?
It depends on the telephony path of the call. Standard Twilio and Plivo calls produce MP3, Exotel produces WAV, and native audio (LiveKit) calls produce OGG (Opus). The download script in this guide reads the file's Content-Disposition header so each recording keeps its correct extension automatically — you don't have to guess.
Can I export recordings for a specific campaign or date range only?
Yes. On the Campaigns page you can select one or more specific campaigns before exporting, and the export respects date filters. The resulting CSV contains only the calls you selected, each with its own permanent Recording URL column.
Is there a limit on how many recordings I can export at once?
A single campaign export returns up to 200,000 call rows, and the agent-level Call Logs export returns up to 50,000 rows. For larger archives, export in batches by date range or by campaign and run the upload script per batch — it is safe to re-run because it skips files already in your bucket.
Will I be charged extra for exporting my recordings?
No. Exporting the CSV and downloading your recordings via the permanent links is included — you own your call data. You only pay your own cloud provider (Google Cloud or AWS) for the storage you consume on their side, which is typically a few cents per GB per month.
Can I automate this so new recordings sync to my bucket continuously?
Yes. Schedule the export + upload script on a cron job (for example, nightly) with a rolling date range, or trigger it from a post-call webhook. Because the script is idempotent — it skips recordings already present in your bucket — running it repeatedly only uploads what is new.
S
Suvojit Mondal
The Edesy engineering team, building voice AI, messaging, and automation products for Indian businesses.
Published
Share
export call recordingscall recordings to google cloudcall recordings to s3ai voice agent data exportgcs upload recordingsvoice agent recordings backup