Skip to content

Download Call Recordings

This guide explains how to archive QVOICE Platform call recording files onto your own systems — for compliance, backup, or integration with internal applications.

You can use either:

  • Pull (API) — Your application lists recordings and downloads audio files over HTTPS.
  • Push (external storage) — The platform uploads each recording to your SFTP server, S3 bucket, or cloud drive after the call.
flowchart LR
  subgraph pull [Pull API]
    App[Customer app] -->|list then download| PortalAPI["Portal API :9443"]
    PortalAPI --> Local[Local disk or NAS]
  end
  subgraph push [Push storage]
    Gorec[Recording pipeline] -->|upload after call| Ext[SFTP or S3 or Drive]
  end

Prerequisites

Requirement Pull (API) Push (storage)
Account with call recording enabled Yes Yes
HTTPS access to the portal API (https://{{ domain }}:9443) Yes Yes (for API config)
API key Yes Admin JWT or API key for config
Local disk, NAS, or object storage for files you download Yes —
Destination credentials (SFTP, S3, etc.) — Yes

Note

If your account uses call recording encryption, downloads through the API return decrypted audio. You do not need to decrypt files yourself.


Choose a method

Goal Recommended method
Selective archive, custom naming, or CRM sync Pull via API
Every recording lands automatically on your server or bucket Push to external storage
Mix of both (push for archive + pull for ad-hoc) Configure push, use pull when needed

Method A — Pull via API

Your application authenticates, lists recordings for a date range, then downloads each audio file.

Authentication

Use an API key. See API Key authentication.

Required headers:

X-API-Key: <your_api_key>
X-Account-ID: <your_account_id>

Base URL pattern:

https://{{ domain }}:9443/api/v2

1. List recordings (metadata)

GET /api/v2/reports/recordings

Provide a date range using one of:

Params Description
timeRange Relative window, e.g. 1d, 1w, 1M
startDate + endDate Unix timestamps; maximum 31 days apart; endDate must be greater than startDate

Pagination:

Param Description
pageSize Page size (must be greater than 0)
startKey Cursor from the previous response next_start_key

Example:

curl -sS "https://{{ domain }}:9443/api/v2/reports/recordings?timeRange=1d&pageSize=50" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "X-Account-ID: YOUR_ACCOUNT_ID"

Response (shape):

{
  "recordings": [
    {
      "id": "202507-aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
    }
  ],
  "next_start_key": ""
}

The list returns metadata only (no audio bytes). Use each recording id in the download step.

2. Download audio by recording ID

GET /api/v2/reports/recordings/{recordingID}
  • Path parameter recordingID format: YYYYMM-{uuid}
  • Response: raw audio (for example audio/mpeg)
  • Header Content-Disposition: attachment; filename=...

Example — save to a local file:

RECORDING_ID="202507-aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"

curl -sS -OJ \
  "https://{{ domain }}:9443/api/v2/reports/recordings/${RECORDING_ID}" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "X-Account-ID: YOUR_ACCOUNT_ID"

-OJ uses the server-provided filename. You can also choose your own path with -o:

curl -sS -o "./archive/${RECORDING_ID}.mp3" \
  "https://{{ domain }}:9443/api/v2/reports/recordings/${RECORDING_ID}" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "X-Account-ID: YOUR_ACCOUNT_ID"

3. Get recordings by call ID

GET /api/v2/reports/recordings/by_call_id/{call_id}

This endpoint returns JSON, not a raw file. Each item includes base64-encoded audio in data:

{
  "recordings": [
    {
      "id": "202507-aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
      "name": "recording.mp3",
      "content_type": "audio/mpeg",
      "data": "<base64>",
      "size": 1234567
    }
  ],
  "total": 1
}

Use this when you already have a call ID (for example from a CDR or CRM). Prefer download-by-recording-ID when you want a simple binary file write.

  1. List recordings for a window (for example yesterday with timeRange=1d, or a fixed startDate/endDate).
  2. For each id, check whether ./archive/{id}.mp3 (or your naming scheme) already exists — skip if present.
  3. Download missing files with GET .../recordings/{id}.
  4. Advance pagination with startKey until next_start_key is empty.
  5. Schedule the job (cron, systemd timer, or orchestrator) to run periodically.

Tip

Keep list windows at or under 31 days when using startDate/endDate. Walk month by month for longer backfills.


Method B — Push to external storage

Configure the account so the recording pipeline uploads files to your SFTP server, S3 (or S3-compatible) bucket, or connected cloud drive after each call. You do not need to poll the API for every file.

Portal UI

  1. Sign in as an account administrator.
  2. Open Configuration → Storage → Recordings (/configuration/storage/recordings).
  3. Select a provider (SFTP, Amazon S3, Google Drive, SharePoint, and others depending on your deployment).
  4. Enter credentials and a base folder if required.
  5. Save — the platform runs a connection test before accepting the configuration.

After a successful save, new recordings are uploaded automatically.

Upload failure alerts

If a recording still cannot be uploaded to the configured storage after the platform's automatic retries — for example expired Google Drive or SharePoint access, a full drive, a deleted folder, or an unreachable SFTP server — the platform emails:

  • every administrator of the account, and
  • every administrator of the reseller that owns the account.

The subject reads "⚠️ Google Drive recording storage is unusable — action may be required" (with the name of your provider). The email lists the account, the provider, the recording ID, the file name and the error the storage provider returned.

To stop the alerts, open Configuration → Storage → Recordings and reconnect or correct the storage configuration. A failed upload does not delete the recording from the platform.

Alerts are rate-limited per account and provider (by default three per hour), so a long outage does not flood your inbox: one email can stand for many failed recordings.

Storage API

Admin-authenticated requests:

Method Path Purpose
GET /api/v2/config/storage/recordings Read current config
PUT /api/v2/config/storage/recordings Create or update config (204 on success)
DELETE /api/v2/config/storage/recordings Remove config

Use the same auth headers as other portal APIs (Authorization: Bearer … or X-API-Key + X-Account-ID).

SFTP — what you must provide

Field JSON key Required
Host auth.sftp.url Yes
Port auth.sftp.port Yes
Username auth.sftp.username Yes
Password auth.sftp.password Yes
Base folder options.recordings.directory Recommended

Example PUT body:

{
  "provider": "sftp",
  "local_storage": false,
  "auth": {
    "sftp": {
      "url": "sftp.example.com",
      "port": 22,
      "username": "recordings",
      "password": "REPLACE_ME"
    }
  },
  "options": {
    "recordings": {
      "directory": "/incoming/call-recordings",
      "date_directory_disabled": false
    }
  }
}
curl -sS -X PUT "https://{{ domain }}:9443/api/v2/config/storage/recordings" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "X-Account-ID: YOUR_ACCOUNT_ID" \
  -H "Content-Type: application/json" \
  -d @sftp-recordings-storage.json

S3 (Amazon or S3-compatible) — what you must provide

Provider value: amazon.

Field JSON key Required
Access key ID auth.amazon.access_key Yes
Secret access key auth.amazon.secret_access_key Yes
Bucket auth.amazon.bucket Yes
Region auth.amazon.region Yes
Custom endpoint (MinIO, etc.) auth.amazon.endpoint Optional
Base folder / prefix options.recordings.directory Recommended

Example PUT body:

{
  "provider": "amazon",
  "local_storage": false,
  "auth": {
    "amazon": {
      "access_key": "AKIA...",
      "secret_access_key": "REPLACE_ME",
      "bucket": "my-call-recordings",
      "region": "us-east-1"
    }
  },
  "options": {
    "recordings": {
      "directory": "archive",
      "date_directory_disabled": false
    }
  }
}

When date_directory_disabled is false (default), uploads typically use date-based subfolders under the base directory.

File names

By default each recording is stored as:

<directory>/<YYYY>/<MM>/call_recording_<call ID>.mp3

The <YYYY>/<MM> subfolders are omitted when date_directory_disabled is true.

To use your own naming, open Config → Call Recordings → General, expand Custom Filename, switch Enabled on and build the Format from the variables shown (CALL_ID, DATETIME, DATETIME_UNIX, DURATION, CALLER_ID_NUMBER, CALLER_ID_NAME, CALLEE_ID_NUMBER, EXTERNAL_NUMBER, EXTENSION, DIRECTION, INTERACTION_ID), then Save Config. The .mp3 extension is added for you. The custom name applies to recordings made after you save; earlier files keep their name.

One call can produce more than one recording

The default name, and a custom format built only from call-level values (CALL_ID, DATETIME, INTERACTION_ID), is the same for every recording of a call. When a call produces two recordings, the second file stored under that name replaces the first. If your recording settings can produce more than one recording per call, add {DURATION} (the recording length in seconds) to the format, for example {CALL_ID}_{DURATION}.

Other providers

Google Drive and Microsoft SharePoint are available on deployments that have the corresponding OAuth application configured. Connect them from Configuration → Storage → Recordings (OAuth consent flow). Deep OAuth setup is outside the scope of this guide.


Security and retention

  • Always use HTTPS. Do not send API keys or storage passwords over plain HTTP.
  • Store API keys and storage secrets in a secrets manager; rotate them periodically.
  • Do not commit credentials into source control or documentation.
  • If a recording was removed by your account retention policy, download requests return 404 with a retention message. Archive via pull or push before retention deletes media if you need long-term copies.
  • Encrypted-at-rest recordings are decrypted for authorized API downloads. See Call Recording encryption.

Warning

Tag or queue restrictions on portal users can return 403 for individual downloads. API keys used for server-to-server archive typically operate with admin-level access for the account — still protect the key as a high-privilege credential.


For help with your deployment, contact info@fonouc.com.