---
audience: developer
summary: Upload, download, transcribe, and browse files attached in a room.
title: Media and files API
path: reference/api/media-and-files
status: published
---

# Media and files API

8 endpoints (6 media + 2 room-files).

## Media

| Method | Path | Purpose |
|---|---|---|
| `POST` | `/v1/media/upload` | Upload a file. Multipart. |
| `GET` | `/v1/media/{media_id}/info` | Metadata (sha256, size, mime, dimensions). |
| `GET` | `/v1/media/download/{media_id}` | Download bytes. |
| `GET` | `/v1/media/download/{media_id}/thumbnail` | Pre-generated thumbnail (images/videos). |
| `GET` | `/v1/media/signed/{media_id}` | Get a short-lived signed URL (no auth header needed). |
| `POST` | `/v1/transcribe` | Submit an audio file for transcription. |

### POST /v1/media/upload

```bash
curl -X POST "$BASE/v1/media/upload" \
  -H "Authorization: Bearer $TOKEN" \
  -F "file=@./photo.jpg" \
  -F "tenant_scope=true"
```

Returns:

```json
{
  "data": {
    "media_id": "med-…",
    "sha256": "abc123…",
    "size": 184234,
    "mime_type": "image/jpeg",
    "thumbnail_id": "med-thumb-…",
    "width": 1920,
    "height": 1080,
    "scan_status": "clean"
  }
}
```

`scan_status` is `pending` immediately, becomes `clean` or
`infected` once ClamAV has scanned. Infected files are auto-deleted;
references to them 404.

### POST /v1/transcribe

```bash
curl -X POST "$BASE/v1/transcribe" \
  -H "Authorization: Bearer $TOKEN" \
  -F "file=@./meeting.m4a" \
  -F "language=en"
```

Async — returns immediately with a `job_id`:

```json
{ "data": { "job_id": "job-…", "status": "queued" } }
```

Poll `GET /v1/jobs/{job_id}` (under
[Queue and scheduled tasks](/docs/scaiwave/reference/api/queue-and-scheduled-tasks))
for completion. The result includes:

```json
{
  "data": {
    "transcript": "Full text…",
    "segments": [{ "start": 0, "end": 3.2, "text": "Hello" }, …],
    "language": "en",
    "duration": 312.4
  }
}
```

## Room file index

Files uploaded into a room are tracked in a per-room index so the
client can show a "Files" tab.

| Method | Path | Purpose |
|---|---|---|
| `GET` | `/v1/rooms/{room_id}/files` | List media attached in this room. |
| `DELETE` | `/v1/rooms/{room_id}/files/{media_id}` | Remove from the index (doesn't delete the media). |

## Storage

All media goes to MinIO/S3 via the path `tenant/{tenant_id}/{sha256}`.
Content-addressable: uploading the same file twice (same sha256)
dedupes server-side.

## Quotas

Per-tenant `max_storage_mb`. Hit returns 413.

## Signed URLs

For clients that can't include auth headers (image tags, embedded
players), use `/v1/media/signed/{media_id}` to get a URL valid for
the next 5 minutes:

```jsonc
{
  "data": {
    "url": "https://...minio.../tenant/.../sha256?X-Amz-...",
    "expires_at": "2026-05-17T14:05:00Z"
  }
}
```
