# GET /v1/videos/by-external-id/{external_video_id}

Look up a video by its platform-specific `external_video_id` (e.g. a TikTok shortcode, a YouTube video ID, an Instagram media ID) and return its full record, latest stats, and 30-day stats history.

The same path is also served by [`PATCH /v1/videos/by-external-id/{external_video_id}`](./by-external-id-patch) for editable metadata updates — this page covers **GET only**.

The `external_video_id` is path-encoded; URLs that contain `/` or other reserved characters must be percent-encoded.

## 📋 Overview

| Property | Value |
|----------|-------|
| **Method** | `GET` |
| **Endpoint** | `https://api.influtics.com/v1/videos/by-external-id/{external_video_id}` |
| **Auth Required** | ✅ |

---

## 🔐 Headers

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

## 📝 Path Parameter

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `external_video_id` | string | ✅ | The platform-specific video ID (e.g. `7489443966908189959` for TikTok, `dQw4w9WgXcQ` for YouTube). |

## 📝 Query Parameters

_None._

## Example Request

```bash
curl -X GET https://api.influtics.com/v1/videos/by-external-id/7489443966908189959 \
  -H "Authorization: Bearer YOUR_API_KEY"
```

## ✅ Response (200 OK)

```json
{
    "success": true,
    "data": {
        "data": {
            "video_id": "77168374-dd2c-496a-b1ae-3d4944f48ccb",
            "external_video_id": "7489443966908189959",
            "stats": {
                "likes":     { "2025-06-30 08:26:03.730104+00": 56500 },
                "views":     { "2025-06-30 08:26:03.730104+00": 1900000 },
                "reposts":   { "2025-06-30 08:26:03.730104+00": 6472 },
                "comments":  { "2025-06-30 08:26:03.730104+00": 483 }
            },
            "views": 2000000,
            "likes": 56501,
            "comments": 526,
            "reposts": 6892,
            "platform": "tiktok",
            "created_at": "2025-06-30T08:25:29.97727+00:00",
            "title": "Untitled",
            "thumbnail_url": "https://p16-sign-sg.tiktokcdn.com/...",
            "cpm": 0,
            "er": 3.339736842105263,
            "stats_last_updated": "2025-06-30T08:26:03.730104+00:00",
            "deleted_at": null,
            "deleted_reason": null
        }
    },
    "meta": {
        "request_id": "9f1b7e7e-1c36-49a9-8521-7b9180d8a1a8",
        "processing_time_ms": 437
    }
}
```

## 📊 GET Response Fields

| Field | Type | Description |
|-------|------|-------------|
| `data.data.video_id` | string | Internal video UUID |
| `data.data.external_video_id` | string | Platform-specific video ID |
| `data.data.stats` | object | Map of metric name → `{ "ISO-8601 timestamp": value }`. Metrics include `likes`, `views`, `reposts`, `comments`. |
| `data.data.views` / `likes` / `comments` / `reposts` | int \| null | Latest recorded value of each metric |
| `data.data.platform` | string | Platform name (e.g. `tiktok`, `instagram`, `youtube`) |
| `data.data.created_at` | string | When the video was first seen (ISO 8601) |
| `data.data.title` | string | Video title / caption (if available) |
| `data.data.thumbnail_url` | string | Video thumbnail URL (if available) |
| `data.data.cpm` | number | Cost per mille (if available) |
| `data.data.payouts` | number \| null | Estimated creator payout. Capped at `budget`. `null` when no budget is set. See [Payouts](./payouts). |
| `data.data.er` | number | Engagement rate (if available) |
| `data.data.stats_last_updated` | string | When stats were last updated (ISO 8601) |
| `data.data.campaign` | string \| null | Campaign name |
| `data.data.notes` | string \| null | Free-form notes |
| `data.data.budget` | number \| null | Total per-video budget |
| `data.data.ecpm` | number \| null | Effective cost per mille (how much to pay the creator for 1k views) |
| `data.data.status` | string \| null | Workflow-state. One of `to do`, `running`, `ended`. |
| `data.data.video_status` | string \| null | Tracking-state. One of `tracking`, `paused`, `deleted`. |
| `data.data.deleted_at` | string \| null | When the video was detected as deleted (ISO 8601). `null` while the video is healthy. See [Deletion state](#deletion-state). |
| `data.data.deleted_reason` | object \| null | Why the video was detected as deleted. `null` unless a reason was recorded. See [Deletion state](#deletion-state). |
| `meta.request_id` | string | Unique request identifier |
| `meta.processing_time_ms` | number | Time taken to process the request (ms) |

> **Metadata fields are named exactly as they are written** on [`POST /v1/videos/track`](./track) and [`PATCH .../by-external-id`](./by-external-id-patch) — a value you set can be read back with the same key. `ecpm` is returned in the same unit it is accepted in; `cpm` above is a separate, platform-computed figure.

---

## Deletion state

Two read-only fields describe why a video stopped producing stats. **Neither is writable** — no endpoint on this API sets them.

| Field | Meaning |
|-------|---------|
| `deleted_at` | ISO 8601 timestamp of the first detection. `null` while the video is healthy. |
| `deleted_reason` | `null`, or an object explaining the cause. |

```json
{
  "video_status": "deleted",
  "deleted_at": "2026-10-18T09:12:44+00:00",
  "deleted_reason": {
    "code": "not_found",
    "message": "video not exist",
    "raw": { "statusCode": 10204, "statusMsg": "video not exist" }
  }
}
```

| `deleted_reason` key | Type | Description |
|----------------------|------|-------------|
| `code` | string | Stable enum: `not_found`, `private`, `classified`, `user_banned`, `geofence_blocked`, or `unavailable`. |
| `message` | string \| null | The platform's own wording, verbatim. Never rewritten. |
| `raw` | object \| null | The untouched provider payload, for debugging. |

The reason does not repeat the platform — it is already on the video's own `platform` field.

> **`code` is inferred, `message` is authoritative.** `code` is derived from the platform's numeric status code, which can be ambiguous — VK's blanket `ACCESS_DENIED` (15) becomes `private`, but the platform never says "private". Prefer `message` in anything user-facing or a model-facing summary.

**Coverage is partial.** Today only **TikTok** and **VK** record a reason. On every other platform `deleted_reason` is `null` even when `video_status` is `deleted` — that means *the provider reported no cause*, not that the video was healthy. Do not treat a `null` reason as "not deleted"; branch on `deleted_at`.

**The two fields are independent of `video_status`.** A video can carry a `deleted_reason` while `video_status` stays `tracking` — this is deliberate for content the platform has suppressed but may restore (TikTok classified/private posts stay retryable so stats reappear if the block lifts). Read `deleted_at` / `deleted_reason`, not `video_status`, to reason about deletion.

**Deletion is reversible.** A video that becomes fetchable again clears both fields to `null` on the next successful refresh. Poll rather than cache a deleted verdict permanently.

---

## ⚠️ Error Responses

All errors share the standard envelope `{ success: false, error: { code, message, request_id, ... } }`. The table below lists every documented `code` for GET.

| Status | `code` | Trigger | Additional `error` fields |
|--------|--------|---------|---------------------------|
| `401` | `INVALID_API_KEY` / `MISSING_AUTH` | Missing or invalid `Authorization` header | — |
| `402` | `PAID_PLAN_REQUIRED` | Free-tier API key attempted the call | `upgrade_url` |
| `404` | `VALIDATION_ERROR` | No video with that `external_video_id` exists for the authenticated org (returns this code rather than `NOT_FOUND` due to a worker-side quirk). | — |
| `500` | `INTERNAL_ERROR` | Server-side error | — |

> **GET 404 envelope note** — due to a known worker-side quirk, the GET endpoint returns a `400 VALIDATION_ERROR`-shaped envelope on 404 (the `code` field is missing; only `message` and `request_id` are populated). If your client switches on `code`, fall back to `status === 404`. A follow-up PR will align this with `PATCH`'s canonical envelope.

## 🎯 Use Cases

- **Single-video lookup** — fetch the current stats snapshot and metadata for a video you already know the ID of.
- **Dashboard deep-links** — open the dashboard directly on a known video without first listing the org's tracked set.
- **Health checks** — confirm a video is still being tracked and that stats are flowing.

---

**Related**: [POST /v1/videos/track →](./track) · [PATCH /v1/videos/by-external-id/{id} →](./by-external-id-patch) · [Get Video Stats →](./stats) · [View Usage →](../account/usage)
