Blog / Tutorials / Tagging Batches with external_id and metadata
Tutorials 6 min read â€ĸ November 13, 2026

Tagging Batches with external_id and metadata

Founder

If you're calling the Scriptube API on behalf of your own customers, you need a way to tell which batch belongs to whom. external_id and metadata do exactly that: tag a batch at submit time, and get the same tag back everywhere the batch shows up — in reads, in exports, and in webhook payloads.

Without this, your only option is maintaining your own lookup table mapping each batch_id Scriptube hands you back to whatever request caused it — workable, but it's one more table to keep in sync, and one more place a webhook arriving before your own write commits can cause a race. Letting Scriptube carry the tag for you removes that table entirely for the common case.

Setting them

Both are optional fields on submit, available on POST /api/v1/transcripts and POST /api/v1/transcripts/resolve:

This is also the piece that makes webhooks usable for a multi-tenant integration in the first place — without it, a webhook tells you a batch finished, but a tenant-serving backend still needs to know whose.

FieldLimitUse it for
external_id128 characters, indexedOne id you can filter on later — a customer id, job id, or ticket number.
metadataJSON object, ≤ 4 KBAnything else worth carrying along — stored verbatim, returned exactly as sent.
import requests

HEADERS = {"X-API-Key": "YOUR_API_KEY"}
BASE = "https://scriptube.me"

resp = requests.post(
    f"{BASE}/api/v1/transcripts",
    headers=HEADERS,
    json={
        "urls": ["https://www.youtube.com/watch?v=dQw4w9WgXcQ"],
        "external_id": "customer_482",
        "metadata": {"workspace_id": "ws_19", "requested_by": "import-job-7"},
    },
)
resp.raise_for_status()
print(resp.json()["batch_id"])

Finding batches by tag

external_id is indexed specifically so you can filter the batch list by it — useful for "show me every batch we've ever run for this customer":

# Later: find every batch created for that customer
batches = requests.get(
    f"{BASE}/api/v1/transcripts",
    headers=HEADERS,
    params={"external_id": "customer_482"},
).json()
for b in batches["batches"]:
    print(b["batch_id"], b["status"], b["external_id"])

A mixed request — videos plus playlists plus channels in one call — can create several batches at once. Each one carries the same submission_id, so GET /api/v1/transcripts?submission_id=... finds every batch one request produced, while external_id finds every batch across many requests for the same tenant. Use whichever matches the question you're asking.

Showing up in webhooks

If you've registered a webhook or passed a callback_url, every event for that batch carries its external_id and metadata in the payload's data object — your receiver doesn't need to call back into the API to know which customer a notification is about:

{
  "event": "batch.completed",
  "timestamp": "2026-10-28T12:00:00Z",
  "data": {
    "batch_id": "b2b1a7b0-...",
    "status": "completed",
    "item_count": 1,
    "completed_count": 1,
    "failed_count": 0,
    "external_id": "customer_482",
    "metadata": {"workspace_id": "ws_19", "requested_by": "import-job-7"}
  }
}

See building a webhook receiver for verifying the signature on that payload before trusting it.

Choosing what goes in each field

external_id is indexed and filterable, so keep it to one short, stable identifier — a customer id or job id, not a sentence. metadata is unindexed and just stored and echoed verbatim, so it's the right place for anything richer: which plan tier the request came from, who on your team triggered it, a correlation id for your own tracing, or flags your own code checks later ({"priority": "high"}). Keep both scoped to what you (the API caller) need — neither field is visible to the end user whose video it is, and neither affects how Scriptube processes the batch.

One thing worth flagging explicitly: both fields are returned verbatim, including inside webhook payloads that go to whatever endpoint you registered. Don't put anything in metadata you wouldn't want sitting in a webhook delivery log — it's for your own bookkeeping, not a secure secret store.

A submit with metadata over 4 KB, or an external_id longer than 128 characters, is rejected with a 422 before anything is created or billed — validate client-side if you're constructing these fields dynamically, to fail fast instead of discovering the limit mid-batch.

Why this matters for multi-tenant apps

If you're embedding Scriptube transcripts in your own SaaS, one API key typically serves many of your customers. Without a tag, a webhook or a batch-list call tells you a batch finished, but not whose. external_id is the cheapest way to close that gap without maintaining your own mapping table — though you'll usually still want your own job id stored for lookups that go the other direction (from your customer's request to the batch, before you know the batch_id).

Frequently asked

Does external_id need to be unique? No — many batches can share the same external_id, which is exactly the point: one customer or job id tagging every batch you've ever run for it.

Can I add or change the tags after a batch is already submitted? No, both fields are set at submit time and are read-only afterward — decide what to tag a batch with before you send the request.

Does this work on the plain submit endpoint too, or only /resolve? Both — external_id, metadata, and callback_url are accepted on POST /api/v1/transcripts and POST /api/v1/transcripts/resolve alike, and on the dashboard's own batch form.

What if I only have submission_id, not external_id, for a batch I'm looking at? GET /api/v1/transcripts/{batch_id} returns both external_id and submission_id on any batch read, plus related_batches for the siblings one mixed request created — you can always get from one to the other.

Next steps

Full reference: API documentation.

Try Scriptube Free

Extract YouTube transcripts instantly. No credit card required.

Get Started

Related Articles

Tutorials

Your YouTube Transcript Library: Find Everything You've Already Processed

Every video you've ever submitted to Scriptube stays in your library — browsable by channel and playlist, searchable when you need to find something you...

Tutorials

Paginating Large YouTube Transcript Batches

A batch with hundreds of videos doesn't come back in one response. GET /api/v1/transcripts/{batch_id} pages its items with skip/limit, and tells you when...

Tutorials

How to Retry Failed YouTube Transcripts Without Resubmitting the Batch

A batch of 50 videos rarely comes back 50 for 50 — a couple of videos might have no captions, one might hit a rate limit, another might just need a second...