Skip to main content
POST
/
recordings
/
{id}
/
upload-url
curl -X POST https://api.bota.dev/v1/recordings/rec_abc123/upload-url \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "type": "audio",
    "content_type": "audio/opus",
    "file_size_bytes": 2048576
  }'
{
  "media_id": "med_001",
  "upload_url": "https://bota-uploads.s3.amazonaws.com/proj_xxx/rec_abc123/med_001/audio.opus?X-Amz-Algorithm=AWS4-HMAC-SHA256&...",
  "expires_at": "2025-01-15T12:00:00Z",
  "headers": {
    "Content-Type": "audio/opus"
  }
}
Generate a pre-signed S3 URL for uploading a file to a recording. Supports audio, image, and video files. Upload the file directly to S3 using the returned URL. After all files are uploaded, call the Complete Upload endpoint.

Authentication

This endpoint accepts two authentication methods:
AuthUse case
API keyBackend uploading on behalf of devices (BLE sync)
Device token4G/WiFi devices uploading directly
curl -X POST https://api.bota.dev/v1/recordings/rec_abc123/upload-url \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "type": "audio",
    "content_type": "audio/opus",
    "file_size_bytes": 2048576
  }'

Path Parameters

id
string
required
The recording’s unique identifier (e.g., rec_abc123).

Request Body

type
string
required
Media type: audio, image, or video.
content_type
string
MIME type of the file. Defaults based on type:
  • Audio (default: audio/opus): audio/opus, audio/wav, audio/mpeg, audio/mp4, audio/webm, audio/ogg, audio/flac
  • Image: image/jpeg, image/png
  • Video: video/mp4
file_size_bytes
integer
Expected file size in bytes. Used for validation.
captured_at
string
ISO 8601 timestamp of when the media was captured. Used to align images/video with the recording timeline. Optional for audio.

Response

Returns a media record ID, a pre-signed S3 upload URL, required headers, and an expiration timestamp.
{
  "media_id": "med_001",
  "upload_url": "https://bota-uploads.s3.amazonaws.com/proj_xxx/rec_abc123/med_001/audio.opus?X-Amz-Algorithm=AWS4-HMAC-SHA256&...",
  "expires_at": "2025-01-15T12:00:00Z",
  "headers": {
    "Content-Type": "audio/opus"
  }
}

Response Fields

FieldTypeDescription
media_idstringIdentifier for the created media record (med_*)
upload_urlstringPre-signed S3 URL for uploading the file via PUT request
expires_atstringISO 8601 timestamp when the upload URL expires (1 hour from creation)
headersobjectHTTP headers to include when uploading to S3 (e.g., Content-Type)

Uploading the File

After obtaining the pre-signed URL, upload the file directly to S3:
cURL
curl -X PUT "https://bota-uploads.s3.amazonaws.com/..." \
  -H "Content-Type: audio/opus" \
  --data-binary @recording.opus
Node.js
const fs = require('fs');

const audioBuffer = fs.readFileSync('recording.opus');

await fetch(upload_url, {
  method: 'PUT',
  headers: {
    'Content-Type': 'audio/opus',
  },
  body: audioBuffer,
});
Python
with open('recording.opus', 'rb') as f:
    audio_data = f.read()

requests.put(
    upload_url,
    headers={'Content-Type': 'audio/opus'},
    data=audio_data,
)

Multi-File Upload

A single recording can have multiple media files. Call this endpoint once per file:
// 1. Create recording
const recording = await createRecording({ device_id: 'dev_abc123' });

// 2. Upload audio
const audio = await getUploadUrl(recording.id, {
  type: 'audio',
  content_type: 'audio/opus',
});
await uploadToS3(audio.upload_url, audioBuffer, 'audio/opus');

// 3. Upload images captured during the recording
const image1 = await getUploadUrl(recording.id, {
  type: 'image',
  content_type: 'image/jpeg',
  captured_at: '2025-01-15T09:05:30Z',
});
await uploadToS3(image1.upload_url, imageBuffer1, 'image/jpeg');

const image2 = await getUploadUrl(recording.id, {
  type: 'image',
  content_type: 'image/jpeg',
  captured_at: '2025-01-15T09:15:00Z',
});
await uploadToS3(image2.upload_url, imageBuffer2, 'image/jpeg');

// 4. Mark complete — fires event for downstream processing
await completeUpload(recording.id);
Upload URLs expire after 1 hour. If expired, request a new URL. After all files are uploaded, call the Complete Upload endpoint to finalize.