Asset endpoints
Upload the base video a campaign personalizes.
Endpoints
| Method | Path | Description |
|---|---|---|
| POST | /campaigns/:id/assets | Upload base video (multipart) |
| POST | /campaigns/:id/assets/from-url | Import base video from a URL instead of uploading it |
| POST | /campaigns/:id/assets/from-library | Reuse a video already in your library |
| GET | /campaigns/:id/assets | List campaign assets |
Accepted formats: video/mp4, video/webm, video/quicktime (.mov). Max size: 200 MB. Campaign must be in draft status.
POST /campaigns/:id/ready answers 409 CAMPAIGN_NOT_READY until that fingerprint lands.The asset object
| Field | Type | Description |
|---|---|---|
| id | string (uuid) | Asset id. |
| campaignId | string (uuid) | The campaign this base video belongs to. |
| fileUrl | string | Storage URL of the stored object. Not directly playable — use previewUrl from the list endpoint. |
| mimeType | string | video/mp4, video/webm or video/quicktime. |
| fileSize | number | Bytes. |
| durationMs | number | omitted | Filled in by the background transcode; absent until then. |
| createdAt | string (ISO 8601) | Ingestion timestamp. |
Reference
POST/campaigns/:id/assets
Upload the base recording as multipart form data.
Success: 201 Created
Path parameters
| Field | Type | Description |
|---|---|---|
| idreq | string (uuid) | Campaign id, in draft status. |
Request
curl -X POST https://api.outvo.io/campaigns/9a8b7c6d-.../assets \
-H "Authorization: Bearer pc_live_xxx" \
-F "file=@base-video.mp4"Response
201 Created
{
"data": {
"id": "3c2b1a09-...",
"campaignId": "9a8b7c6d-...",
"fileUrl": "https://<storage>/assets/<user>/<campaign>/base.mp4",
"mimeType": "video/mp4",
"fileSize": 24117248,
"createdAt": "2026-08-13T12:01:00.000Z"
}
}Errors
| Code | HTTP | When |
|---|---|---|
| CAMPAIGN_NOT_FOUND | 404 | Unknown id, not yours, or soft-deleted. |
| CAMPAIGN_NOT_DRAFT | 409 | The base video cannot be swapped after the campaign leaves draft. |
| VALIDATION_ERROR | 400 | No file part in the request. |
| INVALID_FILE_TYPE | 400 | Not mp4, webm or quicktime. |
| FILE_TOO_LARGE | 400 | Over 200 MB. |
POST/campaigns/:id/assets/from-url
Have Outvo fetch the base recording from a URL. This is the path for tools that cannot post a file — Apify inputs, Clay columns, n8n HTTP nodes, MCP tools.
Success: 201 Created
Headers
| Field | Type | Description |
|---|---|---|
| Idempotency-Key | string | Optional, max 255 characters. See Idempotency. |
Path parameters
| Field | Type | Description |
|---|---|---|
| idreq | string (uuid) | Campaign id, in draft status. |
Body
| Field | Type | Description |
|---|---|---|
| urlreq | string | 1-2048 characters. Must be publicly reachable over http/https; the fetch validates the scheme, resolves and pins DNS, re-checks every redirect hop, caps the download and sniffs the real container type. |
Request
POST /campaigns/9a8b7c6d-.../assets/from-url
{ "url": "https://your-cdn.com/base-recording.mp4" }Response
201 Created
{ "data": { /* the asset object */ } }Errors
| Code | HTTP | When |
|---|---|---|
| CAMPAIGN_NOT_FOUND | 404 | Unknown id, not yours, or soft-deleted. |
| CAMPAIGN_NOT_DRAFT | 409 | The campaign has left draft. |
| VALIDATION_ERROR | 400 | Missing url, an unreachable or disallowed host, a redirect to one, or bytes that are not a supported video container. |
| FILE_TOO_LARGE | 400 | The remote file exceeds 200 MB. |
| IDEMPOTENCY_KEY_REUSED | 422 | Same Idempotency-Key, different body. |
POST/campaigns/:id/assets/from-library
Point the campaign at a recording you already have. No bytes move, so this is the cheapest of the three.
Success: 201 Created
placeholderWord on the response reports the word this endpoint wrote onto the campaign, taken from the recording itself — a Studio entry knows for certain which word was spoken on camera. It is null when the campaign's word was left as it was.
Headers
| Field | Type | Description |
|---|---|---|
| Idempotency-Key | string | Optional, max 255 characters. |
Path parameters
| Field | Type | Description |
|---|---|---|
| idreq | string (uuid) | Campaign id, in draft status. |
Body
| Field | Type | Description |
|---|---|---|
| libraryEntryIdreq | string (uuid) | A library entry id, from GET /video-library or the dashboard's Studio. |
Response
201 Created
{
"data": {
"id": "3c2b1a09-...",
"campaignId": "9a8b7c6d-...",
"fileUrl": "https://<storage>/...",
"mimeType": "video/mp4",
"fileSize": 24117248,
"createdAt": "2026-08-13T12:01:00.000Z",
"libraryEntryId": "7f6e5d4c-...",
"placeholderWord": "there"
}
}Errors
| Code | HTTP | When |
|---|---|---|
| CAMPAIGN_NOT_FOUND | 404 | Unknown campaign id, not yours, or soft-deleted. |
| CAMPAIGN_NOT_DRAFT | 409 | The campaign has left draft. |
| NOT_FOUND | 404 | Unknown library entry, or not yours. |
GET/campaigns/:id/assets
List the campaign's base video with a playable preview link.
Success: 200 OK
previewUrl is signed for one hour and is not part of the stored asset — re-fetch rather than caching it. It is absent if the object could not be signed.
Path parameters
| Field | Type | Description |
|---|---|---|
| idreq | string (uuid) | Campaign id. |
Response
200 OK
{
"data": [
{
"id": "3c2b1a09-...",
"campaignId": "9a8b7c6d-...",
"fileUrl": "https://<storage>/...",
"mimeType": "video/mp4",
"fileSize": 24117248,
"durationMs": 46000,
"createdAt": "2026-08-13T12:01:00.000Z",
"previewUrl": "https://<storage>/...?token=..."
}
]
}Errors
| Code | HTTP | When |
|---|---|---|
| CAMPAIGN_NOT_FOUND | 404 | Unknown id, not yours, or soft-deleted. |
