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

# Complete Upload

> Mark the recording upload as complete

Mark the recording upload as complete after all files have been uploaded to S3. This transitions the recording status from `pending` to `uploaded` and fires an event for downstream processing (e.g., transcription).

## Authentication

This endpoint accepts two authentication methods:

| Auth                            | Use case                                          |
| ------------------------------- | ------------------------------------------------- |
| [API key](/authentication)      | Backend uploading on behalf of devices (BLE sync) |
| [Device token](/authentication) | 4G/WiFi devices uploading directly                |

<RequestExample>
  ```bash cURL (API key) theme={null}
  curl -X POST https://api.bota.dev/v1/recordings/rec_abc123/upload-complete \
    -H "Authorization: Bearer sk_live_..."
  ```

  ```bash cURL (device token) theme={null}
  curl -X POST https://api.bota.dev/v1/recordings/rec_abc123/upload-complete \
    -H "Authorization: Bearer dtok_..."
  ```

  ```javascript Node.js theme={null}
  const response = await fetch('https://api.bota.dev/v1/recordings/rec_abc123/upload-complete', {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer sk_live_...',
    },
  });

  const recording = await response.json();
  console.log(recording.status); // 'uploaded'
  ```

  ```python Python theme={null}
  import requests

  response = requests.post(
      'https://api.bota.dev/v1/recordings/rec_abc123/upload-complete',
      headers={'Authorization': 'Bearer sk_live_...'},
  )

  recording = response.json()
  print(recording['status'])  # 'uploaded'
  ```
</RequestExample>

## Path Parameters

<ParamField path="id" type="string" required>
  The recording's unique identifier (e.g., `rec_abc123`).
</ParamField>

## Response

Returns the updated recording object with status set to `uploaded`.

<ResponseExample>
  ```json 200 theme={null}
  {
    "id": "rec_abc123",
    "device_id": "dev_abc123",
    "end_user_id": "eu_xyz789",
    "name": "Product interview - Jane Doe",
    "status": "uploaded",
    "duration_ms": 1800000,
    "started_at": "2025-01-15T09:00:00Z",
    "ended_at": "2025-01-15T09:30:00Z",
    "transcription_id": null,
    "media": [
      {
        "id": "med_001",
        "type": "audio",
        "mime_type": "audio/opus",
        "file_size_bytes": 2048576,
        "status": "uploaded",
        "captured_at": "2025-01-15T09:00:00Z",
        "created_at": "2025-01-15T10:00:00Z"
      },
      {
        "id": "med_002",
        "type": "image",
        "mime_type": "image/jpeg",
        "file_size_bytes": 245000,
        "status": "uploaded",
        "captured_at": "2025-01-15T09:05:30Z",
        "created_at": "2025-01-15T10:01:00Z"
      }
    ],
    "metadata": {
      "meeting_type": "interview"
    },
    "created_at": "2025-01-15T10:00:00Z"
  }
  ```

  ```json 400 theme={null}
  {
    "error": {
      "code": "invalid_state",
      "message": "Recording is not in pending state"
    }
  }
  ```

  ```json 404 theme={null}
  {
    "error": {
      "code": "not_found",
      "message": "Recording not found"
    }
  }
  ```
</ResponseExample>

## Response Fields

| Field                     | Type            | Description                                             |
| ------------------------- | --------------- | ------------------------------------------------------- |
| `id`                      | string          | The recording's unique identifier                       |
| `device_id`               | string          | The device that created this recording                  |
| `end_user_id`             | string          | The end user associated with this recording             |
| `name`                    | string \| null  | Human-readable name for the recording                   |
| `status`                  | string          | Recording status (will be `uploaded` on success)        |
| `duration_ms`             | integer \| null | Duration of the recording in milliseconds               |
| `started_at`              | string          | ISO 8601 timestamp when the recording started           |
| `ended_at`                | string          | ISO 8601 timestamp when the recording ended             |
| `transcription_id`        | string \| null  | Associated transcription ID, if any                     |
| `media`                   | array           | Files associated with this recording                    |
| `media[].id`              | string          | Media identifier (`med_*`)                              |
| `media[].type`            | string          | Media type: `audio`, `image`, or `video`                |
| `media[].mime_type`       | string          | MIME type of the file                                   |
| `media[].file_size_bytes` | integer         | File size in bytes                                      |
| `media[].status`          | string          | Upload status: `pending`, `uploaded`                    |
| `media[].captured_at`     | string \| null  | ISO 8601 timestamp of when the media was captured       |
| `media[].created_at`      | string          | ISO 8601 timestamp of when the media record was created |
| `metadata`                | object          | Custom metadata attached to the recording               |
| `created_at`              | string          | ISO 8601 timestamp when the recording was created       |

## Complete Upload Flow

```javascript theme={null}
// 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 (optional)
const image = await getUploadUrl(recording.id, {
  type: 'image',
  content_type: 'image/jpeg',
  captured_at: '2025-01-15T09:05:30Z',
});
await uploadToS3(image.upload_url, imageBuffer, 'image/jpeg');

// 4. Mark complete — fires event for downstream processing
const updated = await completeUpload(recording.id);
console.log(updated.status); // 'uploaded'
```

<Note>
  The recording must be in `pending` status. Calling this on an already-uploaded recording returns a `400` error.
</Note>
