# Look up many videos by URL

> For the complete documentation index, see [llms.txt](https://www.shortsintel.com/llms.txt). Every docs page is also available as markdown: add .md to its URL. To connect, read [agents.md](https://www.shortsintel.com/agents.md).

POST `/v1/videos/lookup/batch`

Up to 100 video URLs, any mix of TikTok, Instagram, YouTube and Facebook, in one Run. Each distinct URL is looked up exactly as `POST /v1/videos/lookup` would.

- Validation: every URL is checked before anything runs. One bad URL refuses the whole call with 400 `invalid_request` naming it (`urls[3]`), uncharged.
- Duplicates: the same video however spelled is looked up and charged once, under the first URL that named it.
- Results: `result.items` lists each input with its `video` (the single lookup's shape) or its `error`. The same outcomes are the Run's items at `GET /v1/runs/{id}/items`, free to read again.
- Partial Runs: the Run is `completed` when every URL succeeded, `partial` when some did, `failed` when none did. A URL that is not found, unavailable or whose Source is down is not charged.
- Long batches: over 10 distinct URLs answer 202 with the Run to poll at once unless `wait` is passed; any batch answers 202 past `wait`.

## Price

3 credits per video, charged only for the distinct URLs that succeed. `estimate=true` quotes them all.

Supports `"estimate": true` in the body: the reply is the price in Credits and whether it would be `cached`, with no Run created and nothing charged.

A cached result costs the same as a fresh one; `cached` says which you got.

## Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `Idempotency-Key` | header | `string` | Optional | — | 1-255 printable ASCII characters. The same key and payload returns the original Run (202 while running); a different payload or operation is a 409 `idempotency_conflict`. |

## Request body

`application/json`

object

- `urls` array of string required items 1 to 100 1 to 100 video URLs, any mix of platforms, each one `POST /v1/videos/lookup` accepts. Duplicates (the same video however spelled) are looked up once.
- `wait` number value 0 to 120 Default `30` Seconds to hold the response open before returning a running Run to poll. Default 30, max 120.
- `max_credits` integer value at least 0 As in the query.
- `estimate` boolean If a source the call would read is unavailable and the cache cannot answer, the estimate adds `unavailable` (the `{ platform, capability }` parts it cannot reach), or is a 503 `source_unavailable` when it can reach none.

## Responses

### 200 OK

The Run finished inside the wait budget: `{ run, result }`. The example found one of its two URLs, so the Run is `partial` and charged for one lookup. With `estimate=true`, `{ estimate_credits, cached, items, credits_per_item }` instead.

any of 2 shapes

- Option 1 object The envelope every charged operation answers with: `{ run }` while the Run is running (plus `poll_url` on the 202), joined by `result` once it completes or `error` once it fails.
  - `run` object required Every charged operation creates a Run; the envelope is the same inline or polled.
    - `id` string required
    - `kind` string required What was run, e.g. `video_lookup`.
    - `status` string required one of `running`, `completed`, `partial`, `failed` `partial` keeps the completed items and charges for them, up to what the Balance covers; `failed` charges nothing.
    - `created_at` string required RFC 3339.
    - `completed_at` string required nullable Null while the Run is still running.
    - `charge_credits` integer required What this Run actually cost, in Credits. A stored answer costs the same as a fresh one.
    - `list_price_credits` integer required The full price of the work asked for, in Credits. Equal to `charge_credits` unless the Run charged for only part of it.
    - `cached` boolean required True when a stored result answered without a fresh fetch.
    - `progress` object A running Research's `stage` and counts so far, such as `analysed` of `shortlisted`; a count is absent until known. Absent once the Run settles and on other kinds.
      - `stage` string required one of `keywords`, `searching`, `reading_creators`, `filtering`, `analysing`, `writing_digest`
      - `keywords` integer value at least 0
      - `creators` integer value at least 0
      - `creators_read` integer value at least 0
      - `searches` integer value at least 0
      - `found` integer value at least 0
      - `judged` integer value at least 0
      - `kept` integer value at least 0
      - `dropped_off_topic` integer value at least 0
      - `dropped_unsure` integer value at least 0
      - `shortlisted` integer value at least 0
      - `analysed` integer value at least 0
      - `updated_at` string required
  - `result` object required
    - `requested` integer required Distinct inputs looked up.
    - `completed` integer required Inputs answered, and charged.
    - `failed` integer required Inputs that failed, uncharged.
    - `items` array of object required
      - `input` any The input this item answers, as you sent it.
      - `status` string required one of `completed`, `failed`
      - `video` object On a completed item: the same object the single lookup returns.
        - `platform` string required
        - `id` string required
        - `url` string required nullable
        - `description` string required nullable Null when the read fetched no caption.
        - `posted_at` string required nullable
        - `duration_seconds` number required nullable
        - `thumbnail_url` string required nullable
        - `hashtags` array of string required
        - `sound` object required nullable
          - `id` string required
          - `title` string required nullable
          - `author` string required nullable Who made the sound, as the platform names them. Null when the platform did not say (Facebook never does).
          - `is_original` boolean required nullable True for the creator's own audio, false for a catalogue track. Null when not reported.
        - `creator` object required nullable
          - `id` string required
          - `handle` string required nullable
          - `name` string required
          - `follower_count` number required nullable
          - `avatar_url` string required nullable Our archived copy, else the platform's URL, which may expire: download it rather than store the link.
          - `verified` boolean required nullable The verified badge, from the latest profile read, else this video's. Null when unreported.
        - `region` string required nullable Where the video was posted, as the platform reports it (a country code on TikTok). Null when not reported.
        - `language` string required nullable Platform-declared caption language, e.g. `en` (TikTok). Null when not reported.
        - `title` string required nullable The video's own title (YouTube, Facebook). Null when none or not reported.
        - `commerce` object required nullable What the Platform declares, never inferred from the content. A flag is null when not reported, never false; the object is null when nothing was.
          - `is_ad` boolean required nullable The Platform marks the video as an ad (TikTok, Instagram).
          - `paid_partnership` boolean required nullable The Platform marks a paid partnership (TikTok, Instagram; on YouTube, its "Includes paid promotion" label).
          - `affiliate` boolean required nullable The Platform marks affiliate content (Instagram).
          - `shop_url` string required nullable The product link the video carries (TikTok Shop).
          - `anchors` array of object required The links the video carries under its caption (an editing template, a product, a place). Empty when none were reported.
            - `kind` string required The Platform's own anchor type id, e.g. `54`.
            - `label` string required What the anchor shows, e.g. `CapCut · Editing made easy`.
        - `people` object required nullable Accounts the Video mentions or names (on Instagram, tagged users first). Null when none.
          - `mentions` array of string required Handles the caption mentions (lowercased).
          - `collaborators` array of object required Co-authors the Platform names on the Video (an Instagram collab, a Facebook collaborator).
            - `handle` string required nullable
            - `name` string required nullable
        - `location` object required nullable The place the Video names (Instagram). Null when it named none or the read did not say.
          - `id` string required nullable The Platform's own place id. Null when not sent.
          - `name` string required The place, e.g. `Central Park, New York`.
        - `topics` object required nullable Platform-declared topics. Null when the Platform reported none.
          - `keywords` array of string required The creator's own tags (YouTube).
          - `genre` string required nullable The Platform's category, e.g. `Education` (YouTube).
          - `labels` array of string required The Platform's own category labels (TikTok).
        - `chapters` array of object required nullable The description's chapters, in order (YouTube). Null when the video has none or they were not reported.
          - `title` string required
          - `start_seconds` number required Where the chapter starts, in seconds from the start.
        - `ai_generated` boolean required nullable The Platform labels the video AI-generated (TikTok). Null when not reported.
        - `is_pinned` boolean required nullable Pinned to the top of the creator's feed (Instagram). Null when not reported.
        - `is_sponsored` boolean required nullable Ad flag, #ad or #…partner tag, or a brand collaborator. Null: unchecked.
        - `sponsor` string required nullable The sponsoring brand's handle when is_sponsored.
        - `snapshot` object required nullable Deprecated: use `engagement`.
          - `at` string required
          - `views` number required nullable
          - `likes` number required nullable
          - `comments` number required nullable
          - `shares` number required nullable
          - `saves` number required nullable
        - `engagement` object required nullable A count the platform did not report is null, never 0.
          - `views` number required nullable
          - `likes` number required nullable
          - `comments` number required nullable
          - `shares` number required nullable
          - `saves` number required nullable
          - `at` string required
        - `scores` object required nullable Null when the creator has no baseline yet.
          - `outlier` number required nullable Views over the creator's median views.
          - `outlier_p90` number required nullable
          - `percentile` number required nullable Where this video sits in the creator's own sample, 0-100.
          - `velocity` object required nullable
            - `views_per_hour` number required
            - `window` string required
            - `since` string required nullable When the window starts.
          - `engagement_rate` number required nullable This video's reported interactions (likes, comments, shares, saves) over its views. Null when unknown.
          - `anomaly` boolean required nullable True when `engagement_rate` is under 20% of the baseline's, as paid or boosted views read. Null when unknown.
          - `reason` string required nullable always `aged_out` `aged_out` when the video is over 30 days old and the view Scores are null.
          - `baseline` object required The yardstick every Score on this creator is measured against.
            - `median_views` number required
            - `p90_views` number required
            - `engagement_rate` number required nullable
            - `posts_per_week` number required nullable
            - `sample` integer required
            - `confidence` string required one of `high`, `low`
            - `source` string required one of `videos`, `follower_tier`
            - `computed_at` string required nullable
            - `stale` boolean required True past 7 days; the next lookup refreshes it.
        - `coverage` string always `partial` Present only where the platform's index is best-effort.
      - `error` object On a failed item: why, e.g. `not_found` or `source_unavailable`. Not charged. Its fields are on [Errors](https://www.shortsintel.com/docs/reference/errors).
- Option 2 object Returned instead of a Run when `estimate=true`. Nothing is charged; some operations add members.
  - `estimate_credits` integer required What the call would be charged, in Credits.
  - `cached` boolean required Whether a stored result can already answer. It does not change the price.

### 202 Accepted

Still running: `{ run }`. Poll `GET /v1/runs/{id}`.

object

Still running. Poll `poll_url` (`GET /v1/runs/{id}`), waiting the 202's `Retry-After` seconds between polls.

- `run` object required Every charged operation creates a Run; the envelope is the same inline or polled. Its fields are on [Poll or re-read a run](https://www.shortsintel.com/docs/reference/runs/get-run#response-200).
- `poll_url` string required format `uri` Absolute URL of `GET /v1/runs/{id}`, the body's copy of the `Location` header (for MCP). Present only while `running`.

202 · Example

```json
{
  "run": {
    "id": "run_8f2c1ad04b",
    "kind": "video_lookup_batch",
    "status": "running",
    "created_at": "2026-09-08T09:14:02.418Z",
    "completed_at": null,
    "charge_credits": 0,
    "list_price_credits": 0,
    "cached": false
  },
  "poll_url": "https://www.shortsintel.com/v1/runs/run_8f2c1ad04b"
}
```

### 400 Bad Request

`invalid_request` (a malformed id, `wait`, `estimate` or Idempotency-Key) or `unsupported_platform`, refused before any Run opens. Nothing is charged.

Codes: `invalid_request`, `unsupported_platform`. The problem body and every code are on [Errors](https://www.shortsintel.com/docs/reference/errors).

### 401 Unauthorized

Codes: `missing_authorization`, `invalid_key`, `key_revoked`, `key_expired`, `invalid_token`. The problem body and every code are on [Errors](https://www.shortsintel.com/docs/reference/errors).

### 402 Payment Required

`insufficient_balance`: the Balance cannot cover the quoted price (the worst case for Research and enriched search). No Run opens and nothing is charged; a multi-item Run that runs short later charges its completed items up to the Balance and ends `partial`.

Code: `insufficient_balance`. The problem body and every code are on [Errors](https://www.shortsintel.com/docs/reference/errors).

### 409 Conflict

`idempotency_conflict` — the same Idempotency-Key with a different payload.

Code: `idempotency_conflict`. The problem body and every code are on [Errors](https://www.shortsintel.com/docs/reference/errors).

### 429 Too Many Requests

`rate_limit_exceeded` or `concurrency_limit_exceeded`, with `Retry-After`.

Codes: `rate_limit_exceeded`, `concurrency_limit_exceeded`. The problem body and every code are on [Errors](https://www.shortsintel.com/docs/reference/errors).

### 502 Bad Gateway

A Run that opened and then failed answers `application/problem+json` with its problem's status (`vendor_error` here, `not_found` as 404) and the Run envelope, keeping the Run id. Never charged. The example is a Video lookup's.

Code: `vendor_error`. The problem body and every code are on [Errors](https://www.shortsintel.com/docs/reference/errors).

### 503 Service Unavailable

`source_unavailable`: every source this Run reads failed or is switched off. The Run fails uncharged, envelope alongside; `endpoints` names the source. Retry after `retry_after_seconds` (also `Retry-After`); it does not mean empty. The example is a Video lookup's.

Code: `source_unavailable`. The problem body and every code are on [Errors](https://www.shortsintel.com/docs/reference/errors).

## Related guides

This call creates a Run. With `wait` it holds the response open until the Run finishes or the wait runs out, then answers with the finished result or `202` and the running Run. Poll [`GET /v1/runs/{id}`](https://www.shortsintel.com/docs/reference/runs/get-run) until it settles; re-reading a Run is free.

- [Runs](https://www.shortsintel.com/docs/concepts/runs): How a call becomes a Run, waiting inline, polling and free re-reads.
- [Pricing and estimates](https://www.shortsintel.com/docs/concepts/pricing-and-estimates): Credits, estimates and why a cached result costs the same as a fresh one.
- [Analysis](https://www.shortsintel.com/docs/concepts/analysis): What we derive from a video, why it is computed once and reused, and when asking for a refresh is charged as new work.
- [Scores](https://www.shortsintel.com/docs/concepts/scores): Outlier and velocity, how a creator baseline is built, what confidence means, and when a score stops being reported.

## Request samples

curl:

```bash
curl -X POST "https://www.shortsintel.com/v1/videos/lookup/batch" \
  -H "Authorization: Bearer sk_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"urls":["https://www.tiktok.com/@someone/video/7300000000000000000","https://www.instagram.com/reel/DAbCdEfGhIj/"]}'
```

TypeScript:

```typescript
const response = await fetch(
  "https://www.shortsintel.com/v1/videos/lookup/batch",
  {
    method: "POST",
    headers: {
      "Authorization": "Bearer sk_live_YOUR_KEY",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      "urls": [
        "https://www.tiktok.com/@someone/video/7300000000000000000",
        "https://www.instagram.com/reel/DAbCdEfGhIj/"
      ]
    }),
  },
);

if (!response.ok) {
  throw new Error(`ShortsIntel ${response.status}`);
}

const data = await response.json();
```

Python:

```python
import requests

response = requests.post(
    "https://www.shortsintel.com/v1/videos/lookup/batch",
    headers={
        "Authorization": "Bearer sk_live_YOUR_KEY",
        "Content-Type": "application/json",
    },
    json={
        "urls": [
            "https://www.tiktok.com/@someone/video/7300000000000000000",
            "https://www.instagram.com/reel/DAbCdEfGhIj/",
        ],
    },
)
response.raise_for_status()

data = response.json()
```

200 · The Run finished inside the wait budget: `{ run, result }`. The example found one of its two URLs, so the Run is `partial` and charged for one lookup. With `estimate=true`, `{ estimate_credits, cached, items, credits_per_item }` instead.

```json
{
  "run": {
    "id": "run_8f2c1ad04b",
    "kind": "video_lookup_batch",
    "status": "partial",
    "created_at": "2026-09-08T09:14:02.418Z",
    "completed_at": "2026-09-08T09:14:04.930Z",
    "charge_credits": 3,
    "list_price_credits": 6,
    "cached": false
  },
  "result": {
    "requested": 2,
    "completed": 1,
    "failed": 1,
    "items": [
      {
        "input": "https://www.tiktok.com/@someone/video/7300000000000000000",
        "status": "completed",
        "video": {
          "platform": "tiktok",
          "id": "7300000000000000000",
          "url": "https://www.tiktok.com/@someone/video/7300000000000000000",
          "description": "3 minutes at 4°C every morning for 30 days. Here is what actually changed. #coldplunge #recovery",
          "posted_at": "2026-09-05T06:32:11.000Z",
          "duration_seconds": 41,
          "thumbnail_url": "https://p16-sign.tiktokcdn.com/obj/tos-cover-7300000000000000000.jpeg",
          "hashtags": [
            "coldplunge",
            "recovery",
            "morningroutine"
          ],
          "sound": {
            "id": "7180000000000000000",
            "title": "original sound - someone",
            "author": "someone",
            "is_original": true
          },
          "creator": {
            "id": "cre_9d4b2a7f1c",
            "handle": "someone",
            "name": "Someone",
            "follower_count": 184300,
            "avatar_url": "https://p16-sign.tiktokcdn.com/obj/tos-avatar-someone.jpeg",
            "verified": false
          },
          "region": "US",
          "language": "en",
          "title": null,
          "commerce": {
            "is_ad": false,
            "paid_partnership": null,
            "affiliate": null,
            "shop_url": null,
            "anchors": [
              {
                "kind": "54",
                "label": "CapCut · Editing made easy"
              }
            ]
          },
          "people": {
            "mentions": [
              "coldplungeco"
            ],
            "collaborators": []
          },
          "location": null,
          "topics": {
            "keywords": [],
            "genre": null,
            "labels": [
              "Fitness",
              "Health"
            ]
          },
          "chapters": null,
          "ai_generated": false,
          "is_pinned": null,
          "is_sponsored": null,
          "sponsor": null,
          "snapshot": {
            "at": "2026-09-08T09:14:04.219Z",
            "views": 1284000,
            "likes": 143200,
            "comments": 2810,
            "shares": 9040,
            "saves": 21600
          },
          "engagement": {
            "at": "2026-09-08T09:14:04.219Z",
            "views": 1284000,
            "likes": 143200,
            "comments": 2810,
            "shares": 9040,
            "saves": 21600
          },
          "scores": {
            "outlier": 4.21,
            "outlier_p90": 1.6,
            "percentile": 95,
            "velocity": {
              "views_per_hour": 6698.6,
              "window": "snapshot",
              "since": "2026-10-03T08:00:00.000Z"
            },
            "engagement_rate": 0.1376,
            "anomaly": false,
            "reason": null,
            "baseline": {
              "median_views": 305000,
              "p90_views": 802000,
              "engagement_rate": 0.1342,
              "posts_per_week": 4.1,
              "sample": 20,
              "confidence": "high",
              "source": "videos",
              "computed_at": "2026-09-08T09:14:03.902Z",
              "stale": false
            }
          }
        }
      },
      {
        "input": "https://www.instagram.com/reel/DAbCdEfGhIj/",
        "status": "failed",
        "error": {
          "type": "https://www.shortsintel.com/docs/reference/errors/not_found",
          "title": "Not found",
          "status": 404,
          "code": "not_found",
          "detail": "No video with that id exists on this platform.",
          "retryable": false,
          "action": "Check the id or handle; list your resources to find the right one."
        }
      }
    ]
  }
}
```

200 · With `estimate=true`: the price, no Run, no charge.

```json
{
  "estimate_credits": 6,
  "cached": false,
  "items": 2,
  "credits_per_item": 3
}
```

[Previous Look up a video by URL](https://www.shortsintel.com/docs/reference/videos/lookup-video)[Next Look up a video by id](https://www.shortsintel.com/docs/reference/videos/get-video)
