# POST /v1/videos/track

Start tracking videos across **all supported platforms** — track up to 50 videos in a single request. Each entry in the batch can carry per-video metadata (campaign, tags, notes, ecpm, budget, video_status, status) so you can tag, label, and price videos at the moment you start tracking them.

**Two request shapes are currently accepted** — the new `videos[]` array (recommended) and the legacy `urls` array (deprecated, sunset 2026-10-25). Both shapes return the same per-video response format.

> 🚨 **Deprecation notice (legacy `urls` shape)**
> The legacy `{ "urls": ["..."] }` request body is marked **deprecated** under [RFC 8594](https://datatracker.ietf.org/doc/html/rfc8594). Responses to legacy-shape requests include the following headers:
>
> ```
> Deprecation: true
> Sunset: Sat, 25 Oct 2026 23:59:59 GMT
> ```
>
> After the sunset date the legacy shape returns `400 VALIDATION_ERROR` and clients must migrate to `{ "videos": [{ "url": "...", ... }] }`. New integrations should ignore the legacy shape entirely.

## 📋 Overview

| Property | Value |
|----------|-------|
| **Method** | `POST` |
| **Endpoint** | `https://api.influtics.com/v1/videos/track` |
| **Auth Required** | ✅ |

## 🔐 Headers

```
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
```

## 📝 Request

The `videos[]` shape lets you attach metadata to **specific entries** in the batch. Each entry is a self-contained `{ url, …metadata }` object — URLs and metadata can differ per entry.

### Request Body

| Field | Type | Required | Description | Allowed values |
|-------|------|----------|-------------|----------------|
| `videos` | `array` | ✅ | Up to 50 video entries. Each entry accepts a `url` plus any subset of optional metadata fields below. | 1–50 entries. |
| `videos[].url` | `string` | ✅ | Video URL to track. | Patterns per [Platform Support](../../platform-support). |
| `videos[].campaign` | `string` | ❌ | Campaign name. Empty / omitted = no change. | ≤ 200 chars, trimmed. Allowed: letters, digits, spaces, and `-_.()|:%&`. Forbidden: commas and any other character (e.g. `<`, `>`, `?`, `/`, `[`, `]`, `{`, `}`, `@`, `#`, `;`). |
| `videos[].tags` | `string[]` | ❌ | Tag names. Names are matched case-insensitively; new names auto-create the tag. Empty / omitted = no change. | 1–50 entries, each ≤ 80 chars. ADD-only — existing tags are preserved. |
| `videos[].notes` | `string` | ❌ | Free-form notes (any plain text). | ≤ 2000 chars. |
| `videos[].ecpm` | `number \| null` | ❌ | Effective cost per mille. | float ≥ 0 (e.g. `2.50` ⇒ $2.50 per 1000 views). `null` or empty string clears the field. `0` is a valid value. |
| `videos[].budget` | `integer \| null` | ❌ | Total per-video budget. | >0 integer. `null` / empty string clears. |
| `videos[].video_status` | `string` | ❌ | Tracking-state. | One of `tracking`, `paused`, `deleted`. |
| `videos[].status` | `string` | ❌ | Workflow-state. | One of `to do`, `running`, `ended`. |

> **At least one of `url` is required per entry.** All metadata fields are optional; omit (or send `null` for `ecpm` / `budget`) to leave the corresponding video column unchanged.

### Example Request

```bash
curl -X POST https://api.influtics.com/v1/videos/track \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "videos": [
      {
        "url": "https://www.tiktok.com/@summer.launch/video/7489443966908189959",
        "campaign": "summer_launch_2026",
        "tags": ["fashion", "lifestyle"],
        "notes": "Q3 sponsored post — performance tier A",
        "ecpm": 2.50,
        "budget": 1500,
        "video_status": "tracking",
        "status": "running"
      },
      {
        "url": "https://www.instagram.com/reel/ABC123DEF456/",
        "tags": ["lifestyle"],
        "ecpm": 1.75
      },
      {
        "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
      }
    ]
  }'
```

### Success Response (200 OK)

```json
{
  "success": true,
  "data": {
    "results": [
      {
        "index": 0,
        "url": "https://www.tiktok.com/@summer.launch/video/7489443966908189959",
        "platform": "tiktok",
        "video_id": "77168374-dd2c-496a-b1ae-3d4944f48ccb",
        "external_video_id": "7489443966908189959",
        "status": "tracked",
        "error": null,
        "applied_fields": ["campaign_tag", "tags", "notes", "ecpm", "budget", "video_status", "status"]
      },
      {
        "index": 1,
        "url": "https://www.instagram.com/reel/ABC123DEF456/",
        "platform": "instagram",
        "video_id": "fb14c33f-cb0a-4ddc-b7be-bb9a08e3d969",
        "external_video_id": "ABC123DEF456",
        "status": "already_tracked",
        "error": null,
        "applied_fields": ["tags", "ecpm"]
      },
      {
        "index": 2,
        "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
        "platform": "youtube",
        "video_id": "2b683ee8-ba57-4218-8f7e-2268f58dc8ba",
        "external_video_id": "dQw4w9WgXcQ",
        "status": "tracked",
        "error": null,
        "applied_fields": []
      }
    ]
  },
  "meta": {
    "summary": {
      "total": 3,
      "tracked": 2,
      "already_tracked": 1,
      "errors": 0
    },
    "processing_time_ms": 5421,
    "request_id": "6e10c2ba-dda6-4a3d-92f1-8a224c5a088c"
  }
}
```

### Response Fields

| Field | Type | Description |
|-------|------|-------------|
| `data.results[]` | `array` | One entry per submitted video, in the order submitted. |
| `data.results[].index` | `number` | Zero-based index of the video in the original request array. |
| `data.results[].url` | `string` | The processed video URL (unshortened if applicable). |
| `data.results[].platform` | `string` | Detected platform — see [Platform Support](../../platform-support). |
| `data.results[].video_id` | `string` | Internal Influtics video UUID. |
| `data.results[].external_video_id` | `string` | Platform-specific video ID. |
| `data.results[].status` | `string` | One of `tracked` (newly added), `already_tracked`, or `error`. |
| `data.results[].error` | `string \| null` | Error message when `status` is `error`; `null` otherwise. |
| `data.results[].applied_fields` | `string[]` | The metadata fields that were actually applied for this entry. Empty when the request used the legacy `urls` shape (which carries no metadata). |
| `meta.summary.total` | `number` | Total videos processed. |
| `meta.summary.tracked` | `number` | Videos newly added to tracking. |
| `meta.summary.already_tracked` | `number` | Videos that were already being tracked. |
| `meta.summary.errors` | `number` | Videos that failed to process. |
| `meta.processing_time_ms` | `number` | Server-side processing time. |
| `meta.request_id` | `string` | Unique request identifier. |

> **About `applied_fields`** — reflects **what actually landed in the database** for the entry. If the same URL is submitted twice in one batch (validation rejects this), or if a non-fatal write error occurred (e.g. transient DB blip on `notes` only), the affected field is **omitted** from the array.

## 📦 Request — Legacy (deprecated)

The legacy `{ "urls": ["..."] }` shape is still accepted until **2026-10-25** to give existing integrations time to migrate. New code should send the `videos[]` shape above.

### Request Body

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `urls` | `string[]` | ✅ | Array of video URLs (max 50 per request). |

### Example Request

```bash
curl -X POST https://api.influtics.com/v1/videos/track \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "urls": [
      "https://www.tiktok.com/@username/video/1234567890",
      "https://www.instagram.com/reel/ABC123DEF456/",
      "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
      "https://vt.tiktok.com/ZSHpobCM8BbnH-fbjKy/"
    ]
  }'
```

### Success Response (200 OK) — Legacy

Identical body shape to the `videos[]` response above, except every entry's `applied_fields` is `[]` (no metadata can be supplied via the legacy shape). **Plus** the following response headers:

```
Deprecation: true
Sunset: Sat, 25 Oct 2026 23:59:59 GMT
```

## ⚠️ Error Responses

All errors share the standard envelope `{ success: false, error: { code, message, request_id, ... } }`. The table below lists every documented `code` and the conditions that trigger it.

| Status | `code` | Trigger | Additional `error` fields |
|--------|--------|---------|---------------------------|
| `400` | `VALIDATION_ERROR` | Body is not valid JSON; `videos` is missing / empty / non-array; more than 50 entries; `videos[i]` is not an object; the same URL appears twice in one batch; both `videos` and `urls` are sent in the same request | `field` |
| `400` | `VALIDATION_ERROR` | Per-video metadata validation failed (the `field` identifies which entry + column, e.g. `videos[2].tags` or `videos[0].ecpm`) | `field` |
| `400` | `VALIDATION_ERROR` | Legacy `urls` shape was used after the Sunset date (2026-10-25) | `field: "urls"` |
| `402` | `PAID_PLAN_REQUIRED` | Free-tier API key attempted the call | `upgrade_url` |
| `403` | `SUBSCRIPTION_LIMIT` | Tracking would exceed the org's `tracked_videos` quota (e.g. Pro plan allows 1000, current usage 1000) | `limit`, `current` |
| `429` | `RATE_LIMIT_EXCEEDED` | More than 20 requests/minute (max 50 videos per request), or more than 1000 videos/minute across all requests | — |

> **Per-video entry errors** (e.g. a single bad URL, a single quota-locked creator) are **not** request-level failures — they appear in `data.results[]` with `status: "error"` and a free-text `error` string. The top-level response is still `200 OK`.

## 🔧 Features

### Automatic URL Processing
- **Platform Detection**: Automatically detects platform from URL across all 10 supported networks.
- **URL Unshortening**: Automatically unshortens TikTok shortened URLs (`vt.tiktok.com`, `vm.tiktok.com`) and similar redirects.
- **Duplicate Prevention**: Prevents tracking the same video multiple times within the same org. A duplicate URL inside a single batch is rejected up-front (see `400 VALIDATION_ERROR` above) — disambiguate before re-submitting.
- **Error Isolation**: Per-video failures don't abort the rest of the batch.

### Per-Video Metadata
- Tags are matched **case-insensitively**; new names auto-create the tag row.
- `ecpm` and `budget` accept numbers or numeric strings. `null` or `""` clears the field; negative or non-finite values are rejected.
- `video_status` (`tracking` / `paused` / `deleted`) is independent from `status` (`to do` / `running` / `ended`) — they describe different axes.
- `applied_fields` on each result entry reflects the actual applied columns (`campaign_tag` for the campaign input, etc.) so clients can confirm metadata round-trips.

### Batch Processing Benefits
- **Efficiency**: Process up to 50 videos in a single request.
- **Same Credit Cost**: 30 credits per *newly tracked* video — same per-video cost as the non-batch endpoint.
- **Faster**: Reduced API calls for bulk operations.

## 🎯 Use Cases

- **Influencer Campaigns**: Track video performance for sponsored content and tag each video with campaign + budget at creation time.
- **CRM Imports**: Bulk-discover videos from your data warehouse, ingest them with `status: "to do"` and watch them flow into `running` as they ship.
- **Pricing Benchmarks**: Set `ecpm` per video up-front so dashboards can compare actual eCPM outcomes vs. your pricing assumptions.
- **Content Strategy**: Tag videos by topic (`["fashion", "lifestyle"]`) for downstream segmentation.
- **Brand Monitoring**: Track mentions and branded content at scale, with notes to flag interesting wins/risks for the team.

## 📈 What Happens Next?

1. **First Stats**: First stats observed within **5 minutes**.
2. **30-Day Tracking**: Automatic daily updates for 30 days (renewed on demand thereafter).
3. **Historical Data**: Access to performance trends and growth via [`GET /v1/videos/stats`](./stats).
4. **Dashboard Access**: View all tracked videos in your dashboard, filtered by `campaign`, `tags`, `status`, `video_status`, etc.

## 📊 Rate Limits

- **Multi-Video**: 20 requests per minute (max 50 videos per request).
- **Total Videos**: 1000 videos per minute across all requests.
- **Subscription Limits** (per tier):
  - `tracked_videos_limit` — videos quota enforced up-front; tracking fails with `403 SUBSCRIPTION_LIMIT` if the batch would exceed the cap.

---

**Related**: [Get Video Stats →](./stats) | [View Usage →](../account/usage)
