# API Reference

## Authentication

Authenticate with your API key from [Settings > API Keys](https://renderingvideo.com/settings/apikeys). Requests access only the resources owned by that account.

### Authentication Methods

| Method                     | Format                         | Example                                 |
| -------------------------- | ------------------------------ | --------------------------------------- |
| Bearer Token (Recommended) | `Authorization: Bearer sk-xxx` | `Authorization: Bearer sk-your-api-key` |
| Token Header               | `Authorization: Token sk-xxx`  | `Authorization: Token sk-your-api-key`  |
| X-API-Key Header           | `X-API-Key: sk-xxx`            | `X-API-Key: sk-your-api-key`            |
| Query Parameter            | `?api_key=sk-xxx`              | `/api/v1/video?api_key=sk-your-api-key` |

### Security Note

Never expose your API key in client-side code. Always make API calls from your server.

---

## Endpoints Overview

| Method | Endpoint                          | Description                       |
| ------ | --------------------------------- | --------------------------------- |
| POST   | `/api/v1/video`                   | Create a video task               |
| GET    | `/api/v1/video`                   | List video tasks                  |
| GET    | `/api/v1/video/:taskId`           | Get task details                  |
| DELETE | `/api/v1/video/:taskId`           | Delete a video task               |
| POST   | `/api/v1/video/:taskId/render`    | Trigger rendering                 |
| POST   | `/api/v1/upload`                  | Upload files                      |
| GET    | `/api/v1/files`                   | List uploaded files               |
| DELETE | `/api/v1/files/:fileId`           | Delete a file                     |
| GET    | `/api/v1/credits`                 | Get credit balance                |
| POST   | `/api/v1/preview`                 | Create preview link               |
| GET    | `/api/v1/preview/:tempId`         | Get preview config                |
| DELETE | `/api/v1/preview/:tempId`         | Delete preview link               |
| POST   | `/api/v1/preview/:tempId/convert` | Convert preview to permanent task |

---

## POST /api/v1/video

Create a permanent video task from your video configuration. This does not start rendering.

### Request

```http
POST /api/v1/video
Authorization: Bearer sk-your-api-key
Content-Type: application/json
```

### Request Body

| Field      | Type   | Required | Description                                                  |
| ---------- | ------ | -------- | ------------------------------------------------------------ |
| `config`   | object | Yes      | Video configuration following [JSON Schema](https://renderingvideo.com/docs/json-spec.md) |
| `metadata` | object | No       | Custom metadata to attach to the task                        |

### Example Request

```json
{
  "config": {
    "meta": {
      "version": "2.0.0",
      "width": 1920,
      "height": 1080,
      "fps": 30,
      "background": "#000000"
    },
    "tracks": [
      {
        "clips": [
          {
            "type": "text",
            "text": "Hello World",
            "start": 0,
            "duration": 5
          }
        ]
      }
    ]
  },
  "metadata": {
    "project_id": "proj_123"
  }
}
```

### Success Response (200)

```json
{
  "success": true,
  "taskId": "abc123def456",
  "videoTaskId": "vt_001",
  "previewUrl": "https://video.renderingvideo.com/v/abc123def456",
  "viewerUrl": "https://video.renderingvideo.com/watch/abc123def456",
  "configUrl": "abc123def456.json",
  "status": "created",
  "quality": "1080p",
  "width": 1920,
  "height": 1080,
  "duration": 10,
  "message": "Task created successfully. Call POST /api/v1/video/:taskId/render to start rendering."
}
```

### Response Fields

| Field         | Type   | Description                           |
| ------------- | ------ | ------------------------------------- |
| `taskId`      | string | Unique task identifier                |
| `videoTaskId` | string | Internal video task ID                |
| `previewUrl`  | string | Preview page URL                      |
| `viewerUrl`   | string | Viewer page URL                       |
| `configUrl`   | string | Config file URL                       |
| `status`      | string | Task status (`created`)               |
| `quality`     | string | Video quality (`720p`, `1080p`, `2k`) |
| `width`       | number | Video width in pixels                 |
| `height`      | number | Video height in pixels                |
| `duration`    | number | Video duration in seconds             |

### Error Responses

| Code             | Status          | Description                                  |
| ---------------- | --------------- | -------------------------------------------- |
| `INVALID_CONFIG` | 400             | Invalid video configuration                  |
| `REMOTE_ERROR`   | Upstream status | Remote service error while creating the task |
| `INTERNAL_ERROR` | 500             | Internal server error                        |

---

## GET /api/v1/video

List all video tasks for your account.

### Request

```http
GET /api/v1/video?page=1&limit=20&status=completed
Authorization: Bearer sk-your-api-key
```

### Query Parameters

| Parameter | Type    | Default | Description                      |
| --------- | ------- | ------- | -------------------------------- |
| `page`    | integer | 1       | Page number                      |
| `limit`   | integer | 20      | Items per page (max 100)         |
| `status`  | string  | -       | Filter by the latest task status |

### Success Response (200)

```json
{
  "success": true,
  "tasks": [
    {
      "taskId": "abc123def456",
      "videoTaskId": "vt_001",
      "width": 1920,
      "height": 1080,
      "duration": 10,
      "status": "completed",
      "videoUrl": "https://storage.../videos/abc123.mp4",
      "costCredits": 15,
      "createdAt": "2026-03-19T10:00:00Z",
      "completedAt": "2026-03-19T10:02:30Z"
    }
  ],
  "pagination": {
    "page": 1,
    "limit": 20,
    "total": 45
  }
}
```

---

## GET /api/v1/video/:taskId

Get detailed information about a specific video task.

### Request

```http
GET /api/v1/video/abc123def456
Authorization: Bearer sk-your-api-key
```

### Path Parameters

| Parameter | Type   | Description                        |
| --------- | ------ | ---------------------------------- |
| `taskId`  | string | The task ID returned from creation |

### Success Response (200)

```json
{
  "success": true,
  "taskId": "abc123def456",
  "videoTaskId": "vt_001",
  "width": 1920,
  "height": 1080,
  "duration": 10,
  "status": "completed",
  "videoUrl": "https://storage.../videos/abc123.mp4",
  "previewUrl": "https://video.renderingvideo.com/v/abc123def456",
  "viewerUrl": "https://video.renderingvideo.com/watch/abc123def456",
  "configUrl": "abc123def456.json",
  "costCredits": 15,
  "createdAt": "2026-03-19T10:00:00Z",
  "completedAt": "2026-03-19T10:02:30Z",
  "metadata": {
    "project_id": "proj_123"
  }
}
```

### Task Status Values

| Status      | Description                     |
| ----------- | ------------------------------- |
| `created`   | Task created, not yet rendering |
| `rendering` | Currently rendering             |
| `completed` | Rendering finished successfully |
| `failed`    | Rendering failed                |

---

## DELETE /api/v1/video/:taskId

Permanently delete a video task and its associated render records.

This physically deletes the local task and local render history. The API also attempts to delete the upstream remote task. If upstream deletion fails, the response still succeeds and returns `remoteDeleted: false`.

### Request

```http
DELETE /api/v1/video/abc123def456
Authorization: Bearer sk-your-api-key
```

### Path Parameters

| Parameter | Type   | Description                        |
| --------- | ------ | ---------------------------------- |
| `taskId`  | string | The task ID returned from creation |

### Success Response (200)

```json
{
  "success": true,
  "taskId": "abc123def456",
  "videoTaskId": "vt_001",
  "deleted": true,
  "remoteDeleted": true,
  "message": "Video task deleted successfully"
}
```

### Response Fields

| Field           | Type    | Description                                    |
| --------------- | ------- | ---------------------------------------------- |
| `taskId`        | string  | Remote task ID                                 |
| `videoTaskId`   | string  | Internal video task ID                         |
| `deleted`       | boolean | Whether the local task was permanently deleted |
| `remoteDeleted` | boolean | Whether the upstream remote task was deleted   |
| `message`       | string  | Result message                                 |

### Error Responses

| Code             | Status | Description           |
| ---------------- | ------ | --------------------- |
| `NOT_FOUND`      | 404    | Task not found        |
| `INTERNAL_ERROR` | 500    | Internal server error |

---

## POST /api/v1/video/:taskId/render

Trigger or re-trigger rendering for a video task.

If you need completion notifications, pass `webhook_url`. It is only used for render requests. Creating a task with `POST /api/v1/video` does not accept a public webhook because that endpoint does not start rendering.

### Request

```http
POST /api/v1/video/abc123def456/render
Authorization: Bearer sk-your-api-key
Content-Type: application/json
```

### Request Body

| Field         | Type    | Required | Default | Description                     |
| ------------- | ------- | -------- | ------- | ------------------------------- |
| `webhook_url` | string  | No       | -       | URL for completion notification |
| `num_workers` | integer | No       | 5       | Number of render workers        |

### Success Response (200) - New Render

```json
{
  "success": true,
  "taskId": "abc123def456",
  "renderTaskId": "rt_002",
  "status": "rendering",
  "quality": "1080p",
  "width": 1920,
  "height": 1080,
  "cost": 15,
  "remainingCredits": 970,
  "message": "Rendering started",
  "previewUrl": "https://video.renderingvideo.com/v/abc123def456",
  "viewerUrl": "https://video.renderingvideo.com/watch/abc123def456"
}
```

### Success Response (200) - Already Completed

If the task is already completed with same dimensions:

```json
{
  "success": true,
  "taskId": "abc123def456",
  "renderTaskId": "rt_001",
  "status": "completed",
  "alreadyRendered": true,
  "quality": "1080p",
  "width": 1920,
  "height": 1080,
  "cost": 15,
  "remainingCredits": 985,
  "videoUrl": "https://storage.../videos/abc123.mp4",
  "previewUrl": "https://video.renderingvideo.com/v/abc123def456",
  "viewerUrl": "https://video.renderingvideo.com/watch/abc123def456"
}
```

### Error Responses

| Code                    | Status          | Description               |
| ----------------------- | --------------- | ------------------------- |
| `NOT_FOUND`             | 404             | Task not found            |
| `ALREADY_RENDERING`     | 400             | Task is already rendering |
| `INSUFFICIENT_CREDITS`  | 402             | Not enough credits        |
| `RENDER_TRIGGER_FAILED` | Upstream status | Failed to trigger render  |
| `INTERNAL_ERROR`        | 500             | Internal server error     |

---

## Webhook Notifications

`webhook_url` is supported on render-start endpoints:

- `POST /api/v1/video/:taskId/render`
- `POST /api/v1/preview/:tempId/render`

Behavior:

- The render request stores your `webhook_url` together with the render task
- When the upstream renderer reports `completed` or `failed`, this project first updates its own database
- After the local render record is updated, the project forwards a `POST` request to your `webhook_url`

Forwarded payload shape:

```json
{
  "taskId": "abc123def456",
  "renderTaskId": "rt_002",
  "status": "completed",
  "videoUrl": "https://storage.../videos/abc123.mp4",
  "error": null,
  "timestamp": "2026-03-23T10:00:00.000Z"
}
```

Current implementation notes:

- Only `completed` and `failed` events are forwarded
- `webhook_url` must be a valid absolute URL
- The forward is asynchronous and does not block the upstream webhook response
- There is currently no webhook signature, retry queue, or delivery log in this project

---

## POST /api/v1/upload

Upload image, video, or audio files for use in video configurations.

### Request

```http
POST /api/v1/upload
Authorization: Bearer sk-your-api-key
Content-Type: multipart/form-data
```

### Form Data

| Field   | Type   | Required | Description              |
| ------- | ------ | -------- | ------------------------ |
| `file`  | File   | No\*     | Single file to upload    |
| `files` | File[] | No\*     | Multiple files to upload |

\*At least one of `file` or `files` is required.

### Supported File Types

The upload API accepts any MIME type under these prefixes:

| Category | Accepted MIME Pattern | Common Examples                                                                                                 |
| -------- | --------------------- | --------------------------------------------------------------------------------------------------------------- |
| Image    | `image/*`             | `image/jpeg`, `image/png`, `image/webp`, `image/gif`, `image/svg+xml`, `image/avif`, `image/heic`, `image/heif` |
| Video    | `video/*`             | `video/mp4`, `video/webm`, `video/ogg`, `video/quicktime`                                                       |
| Audio    | `audio/*`             | `audio/mpeg`, `audio/mp3`, `audio/wav`, `audio/ogg`, `audio/aac`, `audio/flac`                                  |

### Storage Limits by Plan

| Plan     | Storage Limit |
| -------- | ------------- |
| Free     | 512 MB        |
| Starter  | 10 GB         |
| Standard | 20 GB         |
| Pro      | 50 GB         |

### Example Request (cURL)

```bash
curl -X POST 'https://renderingvideo.com/api/v1/upload' \
  -H 'Authorization: Bearer sk-your-api-key' \
  -F 'file=@/path/to/image.png' \
  -F 'files=@/path/to/video.mp4'
```

### Success Response (200)

```json
{
  "success": true,
  "message": "Successfully uploaded 2 file(s)",
  "count": 2,
  "assets": [
    {
      "id": "asset_001",
      "name": "image.png",
      "url": "https://storage.../images/2026/03/abc123.png",
      "type": "image",
      "mimeType": "image/png",
      "size": 123456,
      "createdAt": "2026-03-19T10:00:00Z"
    },
    {
      "id": "asset_002",
      "name": "video.mp4",
      "url": "https://storage.../videos/2026/03/def456.mp4",
      "type": "video",
      "mimeType": "video/mp4",
      "size": 5242880,
      "createdAt": "2026-03-19T10:00:01Z"
    }
  ]
}
```

### Error Responses

| Code                     | Status | Description             |
| ------------------------ | ------ | ----------------------- |
| `NO_FILES`               | 400    | No files provided       |
| `UNSUPPORTED_FILE_TYPE`  | 400    | File type not supported |
| `STORAGE_LIMIT_EXCEEDED` | 402    | Storage quota exceeded  |
| `UPLOAD_FAILED`          | 500    | Upload failed           |

### Related File Management Endpoints

- `GET /api/v1/files` lists uploaded files in your library
- `DELETE /api/v1/files/:fileId` removes a file from your library

---

## GET /api/v1/files

List uploaded files for the current API key owner.

### Request

```http
GET /api/v1/files?page=1&limit=20&type=image
Authorization: Bearer sk-your-api-key
```

### Query Parameters

| Parameter | Type    | Default | Description                                    |
| --------- | ------- | ------- | ---------------------------------------------- |
| `page`    | integer | 1       | Page number                                    |
| `limit`   | integer | 20      | Items per page (max 100)                       |
| `type`    | string  | -       | Filter by file type: `image`, `video`, `audio` |

### Success Response (200)

```json
{
  "success": true,
  "files": [
    {
      "id": "asset_001",
      "name": "image.png",
      "url": "https://storage.../images/2026/03/abc123.png",
      "type": "image",
      "mimeType": "image/png",
      "size": 123456,
      "createdAt": "2026-03-19T10:00:00Z"
    }
  ],
  "pagination": {
    "page": 1,
    "limit": 20,
    "total": 12
  }
}
```

### Error Responses

| Code              | Status | Description                                  |
| ----------------- | ------ | -------------------------------------------- |
| `INVALID_REQUEST` | 400    | Invalid `type`, `page`, or `limit` parameter |
| `INTERNAL_ERROR`  | 500    | Internal server error                        |

---

## DELETE /api/v1/files/:fileId

Permanently delete a file from your file library.

This deletes both the stored object and the file record for the current account.

### Request

```http
DELETE /api/v1/files/asset_001
Authorization: Bearer sk-your-api-key
```

### Path Parameters

| Parameter | Type   | Description                                  |
| --------- | ------ | -------------------------------------------- |
| `fileId`  | string | File ID returned from upload or file listing |

### Success Response (200)

```json
{
  "success": true,
  "fileId": "asset_001",
  "deleted": true,
  "message": "File deleted successfully"
}
```

### Error Responses

| Code              | Status | Description           |
| ----------------- | ------ | --------------------- |
| `INVALID_REQUEST` | 400    | Missing file ID       |
| `NOT_FOUND`       | 404    | File not found        |
| `INTERNAL_ERROR`  | 500    | Internal server error |

---

## GET /api/v1/credits

Get your current credit balance.

### Request

```http
GET /api/v1/credits
Authorization: Bearer sk-your-api-key
```

### Success Response (200)

```json
{
  "success": true,
  "credits": 985,
  "currency": "credits"
}
```

---

## POST /api/v1/preview

Create a temporary preview link without consuming credits.

### Request

```http
POST /api/v1/preview
Authorization: Bearer sk-your-api-key
Content-Type: application/json
```

### Request Body

Send the full video schema directly as the request body.

### Example Request Body

```json
{
  "meta": {
    "version": "2.0.0",
    "width": 1920,
    "height": 1080,
    "fps": 30
  },
  "tracks": [
    {
      "clips": [
        {
          "type": "text",
          "start": 0,
          "duration": 3,
          "text": "Temporary Preview"
        }
      ]
    }
  ]
}
```

### Success Response (200)

```json
{
  "success": true,
  "tempId": "temp_abc123",
  "previewUrl": "https://video.renderingvideo.com/t/temp_abc123",
  "viewerUrl": "https://video.renderingvideo.com/preview/temp_abc123",
  "expiresIn": "7d",
  "note": "Preview links are temporary and expire in 7 days. Use /api/v1/video to create permanent tasks."
}
```

### Notes

- Preview links are valid for **7 days**
- Does **not** consume credits
- Does **not** produce a downloadable video file
- Use for testing and previewing configurations

### Related Preview Endpoints

- `GET /api/v1/preview/:tempId` reads the temp config
- `DELETE /api/v1/preview/:tempId` deletes the temp link
- `POST /api/v1/preview/:tempId/convert` clones the temp preview into a permanent task
- `POST /api/v1/preview/:tempId/render` clones the temp preview into a permanent task and immediately renders it

---

## GET /api/v1/preview/:tempId

Get the config behind a temporary preview link.

### Request

```http
GET /api/v1/preview/temp_abc123
Authorization: Bearer sk-your-api-key
```

### Success Response (200)

```json
{
  "success": true,
  "tempId": "temp_abc123",
  "config": {
    "meta": {
      "version": "2.0.0",
      "width": 1920,
      "height": 1080,
      "fps": 30
    },
    "tracks": []
  }
}
```

### Error Responses

| Code             | Status          | Description                 |
| ---------------- | --------------- | --------------------------- |
| `NOT_FOUND`      | 404             | Temp preview not found      |
| `REMOTE_ERROR`   | Upstream status | Failed to read temp preview |
| `INTERNAL_ERROR` | 500             | Internal server error       |

---

## DELETE /api/v1/preview/:tempId

Delete a temporary preview link.

### Request

```http
DELETE /api/v1/preview/temp_abc123
Authorization: Bearer sk-your-api-key
```

### Success Response (200)

```json
{
  "success": true,
  "tempId": "temp_abc123",
  "deleted": true,
  "message": "Temp preview deleted successfully"
}
```

### Error Responses

| Code             | Status          | Description                   |
| ---------------- | --------------- | ----------------------------- |
| `NOT_FOUND`      | 404             | Temp preview not found        |
| `REMOTE_ERROR`   | Upstream status | Failed to delete temp preview |
| `INTERNAL_ERROR` | 500             | Internal server error         |

---

## POST /api/v1/preview/:tempId/convert

Clone a temporary preview into a permanent video task. The temporary link itself remains usable.

### Request

```http
POST /api/v1/preview/temp_abc123/convert
Authorization: Bearer sk-your-api-key
Content-Type: application/json
```

### Request Body

| Field      | Type   | Required | Description                                |
| ---------- | ------ | -------- | ------------------------------------------ |
| `category` | string | No       | Category to use for the new permanent task |

### Success Response (200)

```json
{
  "success": true,
  "tempId": "temp_abc123",
  "converted": true,
  "taskId": "abc123def456",
  "videoTaskId": "vt_001",
  "previewUrl": "https://video.renderingvideo.com/v/abc123def456",
  "viewerUrl": "https://video.renderingvideo.com/watch/abc123def456",
  "configUrl": "abc123def456.json",
  "message": "Temp preview cloned to a permanent task successfully"
}
```

### Error Responses

| Code             | Status          | Description                    |
| ---------------- | --------------- | ------------------------------ |
| `NOT_FOUND`      | 404             | Temp preview not found         |
| `INVALID_CONFIG` | 400             | Temp preview config is invalid |
| `REMOTE_ERROR`   | Upstream status | Failed to convert temp preview |
| `INTERNAL_ERROR` | 500             | Internal server error          |

---

## POST /api/v1/preview/:tempId/render

Clone a temporary preview into a permanent task and immediately start rendering it.

If you need completion notifications for this render, pass `webhook_url`.

### Request

```http
POST /api/v1/preview/temp_abc123/render
Authorization: Bearer sk-your-api-key
Content-Type: application/json
```

### Request Body

| Field         | Type    | Required | Default | Description                                   |
| ------------- | ------- | -------- | ------- | --------------------------------------------- |
| `category`    | string  | No       | `api`   | Category to use for the cloned permanent task |
| `webhook_url` | string  | No       | -       | Completion webhook for the render             |
| `num_workers` | integer | No       | 5       | Number of render workers                      |

### Success Response (200)

```json
{
  "success": true,
  "tempId": "temp_abc123",
  "converted": true,
  "convertMessage": "Temp preview cloned to a permanent task successfully",
  "configUrl": "abc123def456.json",
  "taskId": "abc123def456",
  "renderTaskId": "rt_002",
  "status": "rendering",
  "quality": "1080p",
  "width": 1920,
  "height": 1080,
  "cost": 15,
  "remainingCredits": 970,
  "message": "Rendering started",
  "previewUrl": "https://video.renderingvideo.com/v/abc123def456",
  "viewerUrl": "https://video.renderingvideo.com/watch/abc123def456"
}
```

### Error Responses

| Code                    | Status          | Description                      |
| ----------------------- | --------------- | -------------------------------- |
| `NOT_FOUND`             | 404             | Temp preview not found           |
| `INVALID_CONFIG`        | 400             | Temp preview config is invalid   |
| `ALREADY_RENDERING`     | 400             | Target task is already rendering |
| `INSUFFICIENT_CREDITS`  | 402             | Not enough credits               |
| `RENDER_TRIGGER_FAILED` | Upstream status | Failed to trigger render         |
| `REMOTE_ERROR`          | Upstream status | Failed to convert temp preview   |
| `INTERNAL_ERROR`        | 500             | Internal server error            |

---

## Credit Calculation

- **Cost = Video duration (seconds) × Quality multiplier**

| Quality | Short Edge | Multiplier |
| ------- | ---------- | ---------- |
| 720p    | ≥720px     | 1.0        |
| 1080p   | ≥1080px    | 1.5        |
| 2K      | ≥1440px    | 2.0        |

Quality is automatically determined by the video's shorter dimension.

---

## Error Response Format

All error responses follow this format:

```json
{
  "success": false,
  "error": "Human-readable error message",
  "code": "ERROR_CODE",
  "details": {
    "field": "additional context"
  }
}
```

### Common Error Codes

| Code                     | HTTP Status     | Description                        |
| ------------------------ | --------------- | ---------------------------------- |
| `MISSING_API_KEY`        | 401             | No API key provided                |
| `INVALID_API_KEY`        | 401             | Invalid or revoked API key         |
| `INVALID_API_KEY_FORMAT` | 401             | API key format is incorrect        |
| `API_KEY_INACTIVE`       | 401             | API key is inactive or revoked     |
| `USER_NOT_FOUND`         | 401             | API key owner was not found        |
| `INVALID_REQUEST`        | 400             | Request parameters are invalid     |
| `INVALID_CONFIG`         | 400             | Video configuration is invalid     |
| `INSUFFICIENT_CREDITS`   | 402             | Not enough credits                 |
| `NOT_FOUND`              | 404             | Resource not found                 |
| `ALREADY_RENDERING`      | 400             | Task is already rendering          |
| `REMOTE_ERROR`           | Upstream status | Upstream service returned an error |
| `RENDER_TRIGGER_FAILED`  | Upstream status | Render start request failed        |
| `INTERNAL_ERROR`         | 500             | Server error                       |

## SDK and API updates

Use `GET /api/v1/capabilities` to inspect the deployed schema version, supported clip/animation/transition types, render qualities and feature flags. Older deployments may return 404 for this new endpoint.

Task creation accepts optional `title` (1–255 characters), `category` (1–100 characters, default `api`) and object `metadata`. Task list/detail responses include title and category. Listing defaults to `category=api`; use `category=all` to include website-created tasks, or a specific category. The value `all` is reserved for listing.

Preview conversion and preview rendering accept `metadata` and `category`. `POST /api/v1/preview/:tempId/render` converts and starts rendering in one call. Render bodies use `webhook_url` and positive integer `num_workers` (default 5). An omitted body means default options. Invalid JSON/options return `INVALID_REQUEST` (400); invalid video schemas return `INVALID_CONFIG` (400).

Authenticated preview responses expose absolute `previewUrl`, `playerUrl`, and `viewerUrl` when supplied upstream; preserve `tempId` and `expiresIn`. Quality is derived from schema dimensions; no separate quality option is accepted.