curl -X POST https://api.shortkit.dev/v1/content/import \
-H "Authorization: Bearer sk_live_your_secret_key" \
-H "Content-Type: application/json" \
-d '{
"callbackUrl": "https://your-backend.com/webhooks/shortkit",
"items": [
{
"contentType": "video",
"title": "Quarterly Recap",
"sourceUrl": "https://storage.example.com/videos/recap-q1.mp4",
"externalId": "vid-q1-2026",
"tags": ["recap", "quarterly"],
"generateSubtitles": true
},
{
"contentType": "video",
"title": "Product Launch",
"sourceUrl": "https://storage.example.com/videos/launch.mp4",
"externalId": "vid-launch-spring",
"section": "Product",
"priority": "high"
},
{
"contentType": "carousel",
"title": "Team Photos",
"images": [
{"sourceUrl": "https://storage.example.com/photos/team-1.jpg", "altText": "Engineering team"},
{"sourceUrl": "https://storage.example.com/photos/team-2.jpg", "altText": "Design team"}
]
}
]
}'
import requests
resp = requests.post(
"https://api.shortkit.dev/v1/content/import",
headers={
"Authorization": "Bearer sk_live_your_secret_key",
"Content-Type": "application/json",
},
json={
"callbackUrl": "https://your-backend.com/webhooks/shortkit",
"items": [
{
"contentType": "video",
"title": "Quarterly Recap",
"sourceUrl": "https://storage.example.com/videos/recap-q1.mp4",
"externalId": "vid-q1-2026",
"tags": ["recap", "quarterly"],
"generateSubtitles": True,
},
{
"contentType": "video",
"title": "Product Launch",
"sourceUrl": "https://storage.example.com/videos/launch.mp4",
"externalId": "vid-launch-spring",
"section": "Product",
},
],
},
)
result = resp.json()
print(f"Imported {result['summary']['succeeded']} of {result['summary']['total']}")
const resp = await fetch("https://api.shortkit.dev/v1/content/import", {
method: "POST",
headers: {
Authorization: "Bearer sk_live_your_secret_key",
"Content-Type": "application/json",
},
body: JSON.stringify({
callbackUrl: "https://your-backend.com/webhooks/shortkit",
items: [
{
contentType: "video",
title: "Quarterly Recap",
sourceUrl: "https://storage.example.com/videos/recap-q1.mp4",
externalId: "vid-q1-2026",
tags: ["recap", "quarterly"],
generateSubtitles: true,
},
{
contentType: "video",
title: "Product Launch",
sourceUrl: "https://storage.example.com/videos/launch.mp4",
externalId: "vid-launch-spring",
section: "Product",
},
],
}),
});
const { data, summary } = await resp.json();
console.log(`Imported ${summary.succeeded} of ${summary.total}`);
{
"data": [
{
"id": "d290f1ee-6c54-4b01-90e6-d701748f0851",
"contentType": "video",
"title": "Quarterly Recap",
"status": "queued",
"priority": "normal",
"error": null
},
{
"id": "e3a1b2c3-d4e5-6789-abcd-ef0123456789",
"contentType": "video",
"title": "Product Launch",
"status": "queued",
"priority": "normal",
"error": null
},
{
"id": "f4b2c3d4-e5f6-7890-bcde-f01234567890",
"contentType": "carousel",
"title": "Team Photos",
"status": "ready",
"error": null,
"images": [
{"position": 0, "imageUrl": "https://...", "altText": "Engineering team"},
{"position": 1, "imageUrl": "https://...", "altText": "Design team"}
]
}
],
"summary": {
"total": 3,
"succeeded": 3,
"failed": 0
},
"meta": {
"request_id": "req_import001"
}
}
Content
Import Content
Import content from URLs in bulk. ShortKit fetches and processes the media for you.
POST
/
v1
/
content
/
import
curl -X POST https://api.shortkit.dev/v1/content/import \
-H "Authorization: Bearer sk_live_your_secret_key" \
-H "Content-Type: application/json" \
-d '{
"callbackUrl": "https://your-backend.com/webhooks/shortkit",
"items": [
{
"contentType": "video",
"title": "Quarterly Recap",
"sourceUrl": "https://storage.example.com/videos/recap-q1.mp4",
"externalId": "vid-q1-2026",
"tags": ["recap", "quarterly"],
"generateSubtitles": true
},
{
"contentType": "video",
"title": "Product Launch",
"sourceUrl": "https://storage.example.com/videos/launch.mp4",
"externalId": "vid-launch-spring",
"section": "Product",
"priority": "high"
},
{
"contentType": "carousel",
"title": "Team Photos",
"images": [
{"sourceUrl": "https://storage.example.com/photos/team-1.jpg", "altText": "Engineering team"},
{"sourceUrl": "https://storage.example.com/photos/team-2.jpg", "altText": "Design team"}
]
}
]
}'
import requests
resp = requests.post(
"https://api.shortkit.dev/v1/content/import",
headers={
"Authorization": "Bearer sk_live_your_secret_key",
"Content-Type": "application/json",
},
json={
"callbackUrl": "https://your-backend.com/webhooks/shortkit",
"items": [
{
"contentType": "video",
"title": "Quarterly Recap",
"sourceUrl": "https://storage.example.com/videos/recap-q1.mp4",
"externalId": "vid-q1-2026",
"tags": ["recap", "quarterly"],
"generateSubtitles": True,
},
{
"contentType": "video",
"title": "Product Launch",
"sourceUrl": "https://storage.example.com/videos/launch.mp4",
"externalId": "vid-launch-spring",
"section": "Product",
},
],
},
)
result = resp.json()
print(f"Imported {result['summary']['succeeded']} of {result['summary']['total']}")
const resp = await fetch("https://api.shortkit.dev/v1/content/import", {
method: "POST",
headers: {
Authorization: "Bearer sk_live_your_secret_key",
"Content-Type": "application/json",
},
body: JSON.stringify({
callbackUrl: "https://your-backend.com/webhooks/shortkit",
items: [
{
contentType: "video",
title: "Quarterly Recap",
sourceUrl: "https://storage.example.com/videos/recap-q1.mp4",
externalId: "vid-q1-2026",
tags: ["recap", "quarterly"],
generateSubtitles: true,
},
{
contentType: "video",
title: "Product Launch",
sourceUrl: "https://storage.example.com/videos/launch.mp4",
externalId: "vid-launch-spring",
section: "Product",
},
],
}),
});
const { data, summary } = await resp.json();
console.log(`Imported ${summary.succeeded} of ${summary.total}`);
{
"data": [
{
"id": "d290f1ee-6c54-4b01-90e6-d701748f0851",
"contentType": "video",
"title": "Quarterly Recap",
"status": "queued",
"priority": "normal",
"error": null
},
{
"id": "e3a1b2c3-d4e5-6789-abcd-ef0123456789",
"contentType": "video",
"title": "Product Launch",
"status": "queued",
"priority": "normal",
"error": null
},
{
"id": "f4b2c3d4-e5f6-7890-bcde-f01234567890",
"contentType": "carousel",
"title": "Team Photos",
"status": "ready",
"error": null,
"images": [
{"position": 0, "imageUrl": "https://...", "altText": "Engineering team"},
{"position": 1, "imageUrl": "https://...", "altText": "Design team"}
]
}
],
"summary": {
"total": 3,
"succeeded": 3,
"failed": 0
},
"meta": {
"request_id": "req_import001"
}
}
Import up to 100 content items in a single request. Provide source URLs (cloud storage, CDN, public URLs) and ShortKit downloads, processes, and creates the content.
Use this when you have media hosted elsewhere — migrating from another platform, importing from a CMS, or pulling from cloud storage.
Every video item in the response reports its priority:
Priority applies to this endpoint only — the TikTok and Instagram import endpoints do not take a
The
The callback includes the
Request body
string
A webhook URL that ShortKit will POST to as each item progresses through the import pipeline. See Callback lifecycle for the payload shape and delivery behavior.
object[]
required
Array of content items to import. Min 1, max 100 per request.Each item accepts the same metadata fields as Upload (title, description, tags, section, author, customMetadata, publishAt, expiresAt, generateSubtitles, autoScrollInterval), plus:
protected(boolean) — Whentrue, playback is gated behind a short-lived signed streaming URL. When omitted, defaults to your organization’s default. See Protected Content.
sourceUrl(string, required) — URL to the video file.externalId(string) — Your own identifier for this content. Used for idempotency — if you re-import an item with the sameexternalId, ShortKit skips it instead of creating a duplicate.priority(string) —"high"or"normal". Defaults to"normal". A"high"item is processed ahead of every"normal"import. See Processing priority.
images(object[], required) — Array of image objects, each with:sourceUrl(string, required) — URL to the image file.altText(string) — Accessibility text.position(integer) — Display order (0-indexed).
externalId(string) — Same idempotency behavior as videos.
POST /v1/content/{id}/videos.curl -X POST https://api.shortkit.dev/v1/content/import \
-H "Authorization: Bearer sk_live_your_secret_key" \
-H "Content-Type: application/json" \
-d '{
"callbackUrl": "https://your-backend.com/webhooks/shortkit",
"items": [
{
"contentType": "video",
"title": "Quarterly Recap",
"sourceUrl": "https://storage.example.com/videos/recap-q1.mp4",
"externalId": "vid-q1-2026",
"tags": ["recap", "quarterly"],
"generateSubtitles": true
},
{
"contentType": "video",
"title": "Product Launch",
"sourceUrl": "https://storage.example.com/videos/launch.mp4",
"externalId": "vid-launch-spring",
"section": "Product",
"priority": "high"
},
{
"contentType": "carousel",
"title": "Team Photos",
"images": [
{"sourceUrl": "https://storage.example.com/photos/team-1.jpg", "altText": "Engineering team"},
{"sourceUrl": "https://storage.example.com/photos/team-2.jpg", "altText": "Design team"}
]
}
]
}'
import requests
resp = requests.post(
"https://api.shortkit.dev/v1/content/import",
headers={
"Authorization": "Bearer sk_live_your_secret_key",
"Content-Type": "application/json",
},
json={
"callbackUrl": "https://your-backend.com/webhooks/shortkit",
"items": [
{
"contentType": "video",
"title": "Quarterly Recap",
"sourceUrl": "https://storage.example.com/videos/recap-q1.mp4",
"externalId": "vid-q1-2026",
"tags": ["recap", "quarterly"],
"generateSubtitles": True,
},
{
"contentType": "video",
"title": "Product Launch",
"sourceUrl": "https://storage.example.com/videos/launch.mp4",
"externalId": "vid-launch-spring",
"section": "Product",
},
],
},
)
result = resp.json()
print(f"Imported {result['summary']['succeeded']} of {result['summary']['total']}")
const resp = await fetch("https://api.shortkit.dev/v1/content/import", {
method: "POST",
headers: {
Authorization: "Bearer sk_live_your_secret_key",
"Content-Type": "application/json",
},
body: JSON.stringify({
callbackUrl: "https://your-backend.com/webhooks/shortkit",
items: [
{
contentType: "video",
title: "Quarterly Recap",
sourceUrl: "https://storage.example.com/videos/recap-q1.mp4",
externalId: "vid-q1-2026",
tags: ["recap", "quarterly"],
generateSubtitles: true,
},
{
contentType: "video",
title: "Product Launch",
sourceUrl: "https://storage.example.com/videos/launch.mp4",
externalId: "vid-launch-spring",
section: "Product",
},
],
}),
});
const { data, summary } = await resp.json();
console.log(`Imported ${summary.succeeded} of ${summary.total}`);
{
"data": [
{
"id": "d290f1ee-6c54-4b01-90e6-d701748f0851",
"contentType": "video",
"title": "Quarterly Recap",
"status": "queued",
"priority": "normal",
"error": null
},
{
"id": "e3a1b2c3-d4e5-6789-abcd-ef0123456789",
"contentType": "video",
"title": "Product Launch",
"status": "queued",
"priority": "normal",
"error": null
},
{
"id": "f4b2c3d4-e5f6-7890-bcde-f01234567890",
"contentType": "carousel",
"title": "Team Photos",
"status": "ready",
"error": null,
"images": [
{"position": 0, "imageUrl": "https://...", "altText": "Engineering team"},
{"position": 1, "imageUrl": "https://...", "altText": "Design team"}
]
}
],
"summary": {
"total": 3,
"succeeded": 3,
"failed": 0
},
"meta": {
"request_id": "req_import001"
}
}
Processing behavior
- Videos are queued for async processing. They return with
status: "queued"and transition toprocessingonce transcoding begins, thenreadyonce it completes. This typically takes 30–120 seconds depending on file size. - Carousels are processed synchronously. Images are fetched and stored immediately — carousels in the response are already
ready.
error message in the response. Other items in the batch are not affected.
Processing priority
Imports are generally processed in the order they arrive. Setpriority: "high" on an item that shouldn’t wait its turn (ex. a time sensitive, user facing import, rather than a catalog backfill) and it jumps ahead of every item at the default priority still queued, including ones submitted earlier.
{
"items": [
{
"contentType": "video",
"title": "User upload — Priya M.",
"sourceUrl": "https://cdn.example.com/uploads/priya-m-4711.mp4",
"externalId": "user-upload-4711",
"priority": "high"
},
{
"contentType": "video",
"title": "Catalog backfill 0001",
"sourceUrl": "https://cdn.example.com/catalog/0001.mp4",
"externalId": "catalog-0001"
}
]
}
{
"data": [
{
"id": "d290f1ee-6c54-4b01-90e6-d701748f0851",
"externalId": "user-upload-4711",
"contentType": "video",
"title": "User upload — Priya M.",
"status": "queued",
"priority": "high",
"error": null
},
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef0123456789",
"externalId": "catalog-0001",
"contentType": "video",
"title": "Catalog backfill 0001",
"status": "queued",
"priority": "normal",
"error": null
}
]
}
priority, and their items are processed at normal priority.
Callback lifecycle
When you provide acallbackUrl, ShortKit sends a POST request to that URL at each stage of the import pipeline. Your endpoint must respond with a 2xx status code to acknowledge receipt.
Callback events
ShortKit delivers up to three callbacks per video item as it moves through the pipeline: 1.content.imported with status: "processing"
Sent when the video has been accepted for transcoding. This is where you receive the playbackId — store it in your backend alongside the content record.
{
"event": "content.imported",
"contentId": "d290f1ee-6c54-4b01-90e6-d701748f0851",
"status": "processing",
"playbackId": "rJ4OE02Z0Z4IY01Eo4Vgup5OilG8xZB9",
"title": "Quarterly Recap",
"source": "url",
"customMetadata": { "campaign": "q1-recap" },
"timestamp": "2026-03-10T14:30:00.000Z"
}
customMetadata field echoes the content’s custom metadata. For platform imports it carries the auto-extracted fields — for TikTok that’s tiktok_id, tiktok_url, tiktok_likes, tiktok_views, tiktok_comments, tiktok_author_name, tiktok_upload_date, and tiktok_timestamp (see Auto-populated metadata). It is {} when the content has no custom metadata.
At this point the video is streamable (progressive delivery) but transcoding is not yet complete. If you plan to serve this content immediately, be aware that viewers of longer videos may experience buffering until encoding finishes.
2. content.imported with status: "ready"
Sent when transcoding completes and the video is fully available at all quality levels. The payload shape is identical to the processing callback, with status set to "ready".
3. content.import_failed with status: "error"
Sent if transcoding fails. The payload includes an error field describing the failure. No playbackId is included.
{
"event": "content.import_failed",
"contentId": "e3a1b2c3-d4e5-6789-abcd-ef0123456789",
"status": "error",
"error": "Video processing failed",
"title": "Product Launch",
"source": "url",
"customMetadata": { "campaign": "spring-launch" },
"timestamp": "2026-03-10T14:35:00.000Z"
}
Retry behavior
Each callback is attempted up to 3 times. If your endpoint returns a non-2xx status code or is unreachable, ShortKit retries after 1 second, then again after 5 seconds. If all three attempts fail, the callback is abandoned. Callbacks are fire-and-forget — a failed callback does not affect the import itself. The video continues processing regardless.Handling callback failures
If your webhook endpoint was down and you missed a callback, you can always retrieve the current status and playback ID by polling the content endpoint:curl https://api.shortkit.dev/v1/content/d290f1ee-6c54-4b01-90e6-d701748f0851 \
-H "Authorization: Bearer sk_live_your_secret_key"
playbackId once the video has been accepted for processing. The SDK uses this playback ID to resolve streaming and thumbnail URLs.
Callbacks are only sent for video imports. Carousels are processed synchronously and are already
ready in the initial import response.Idempotency
SetexternalId on each item to prevent duplicate imports. If you submit an item with an externalId that already exists in your organization, ShortKit skips it and returns the existing content record. This makes it safe to retry failed import requests without worrying about creating duplicates.
Use whatever stable identifier you already have for each content record in your own system — a database primary key, a CMS slug, an internal content ID. As long as you use the same externalId for the same piece of content, ShortKit will recognize it as a duplicate and skip re-importing it. externalId is scoped to your organization, so different organizations can use the same values independently.
The returned record keeps the values it was created with, including its priority — resubmitting an existing externalId at a higher priority returns the original item unchanged rather than raising it.
TikTok import
ShortKit imports content directly from TikTok viaPOST /v1/content/import/tiktok. It extracts metadata from the source automatically — you don’t provide title, description, or author.
Each URL you submit is either a single video or a creator profile (for example https://www.tiktok.com/@username). A profile URL is expanded automatically into that creator’s most recent videos.
Request body
string[]
TikTok URLs to import. Min 1, max 20 per request. Each entry is either a single video URL or a creator profile URL. Submit at most one profile URL per request — pair it with single video URLs if you need to, and send additional profiles as separate requests. Provide exactly one of
urls or items.object[]
Alternative to The Instagram endpoint accepts the same
urls that lets you attach your own metadata per URL. Min 1, max 20 per request; the same URL rules as urls apply. Provide exactly one of urls or items. Each item:url(string, required) — a single video URL or a creator profile URL.customMetadata(object) — string key/value pairs stamped onto the imported content at creation, stored alongside the auto-populatedtiktok_*fields. A profile URL’scustomMetadatais applied to every video expanded from that profile. The auto-populatedtiktok_*keys are reserved — the importer’s values win on collision. If a URL is skipped as a duplicate, its metadata is skipped with it.externalId(string) — Your own identifier for this content, used for idempotency exactly like the Import endpoint — re-importing with the sameexternalIdreturns the existing content instead of creating a duplicate. When omitted, ShortKit derives one from the platform (tiktok_<id>/instagram_<id>), which is what deduplicates repeat imports of the same video by default. Ignored on profile URLs (one id can’t apply across an expansion).
{
"items": [
{ "url": "https://www.tiktok.com/@nasa/video/123", "customMetadata": { "sku": "A1" }, "externalId": "my-cms-123" },
{ "url": "https://www.tiktok.com/@nasa", "customMetadata": { "campaign": "space-q3" } }
]
}
items shape.integer
default:"20"
Maximum number of recent videos to import from each profile URL. Defaults to
20, up to 10000. Ignored for single video URLs.integer
Import a profile’s most-viewed videos instead of its latest. When set to
N (≥1), ShortKit scans a window of the profile’s recent videos and imports only the N with the highest view count — maxVideosPerProfile is ignored in this mode. When omitted, the latest maxVideosPerProfile videos are imported (default behavior).Only the selected videos are downloaded and processed — the rest of the scanned window is ranked on metadata alone and never ingested. Applies to profile URLs only.string
A webhook URL that ShortKit POSTs to as each imported item progresses. Uses the same per-item payload and delivery behavior as the callback lifecycle above, plus a job-level callback when the import completes (see Tracking progress).
curl -X POST https://api.shortkit.dev/v1/content/import/tiktok \
-H "Authorization: Bearer sk_live_your_secret_key" \
-H "Content-Type: application/json" \
-d '{
"urls": ["https://www.tiktok.com/@nasa"],
"maxVideosPerProfile": 50,
"callbackUrl": "https://your-backend.com/webhooks/shortkit"
}'
import requests
resp = requests.post(
"https://api.shortkit.dev/v1/content/import/tiktok",
headers={
"Authorization": "Bearer sk_live_your_secret_key",
"Content-Type": "application/json",
},
json={
"urls": ["https://www.tiktok.com/@nasa"],
"maxVideosPerProfile": 50,
"callbackUrl": "https://your-backend.com/webhooks/shortkit",
},
)
job = resp.json()["data"]
print(f"Job {job['jobId']}: {job['profilesQueued']} profile(s) queued")
const resp = await fetch("https://api.shortkit.dev/v1/content/import/tiktok", {
method: "POST",
headers: {
Authorization: "Bearer sk_live_your_secret_key",
"Content-Type": "application/json",
},
body: JSON.stringify({
urls: ["https://www.tiktok.com/@nasa"],
maxVideosPerProfile: 50,
callbackUrl: "https://your-backend.com/webhooks/shortkit",
}),
});
const { data } = await resp.json();
console.log(`Job ${data.jobId}: ${data.profilesQueued} profile(s) queued`);
Response
TikTok imports are asynchronous. The endpoint returns a job summary, not the content records. A profile URL is expanded in the background, so the number of videos it produces is not known when the request returns —videosQueued reflects only single video URLs you submitted directly.
201
{
"data": {
"jobId": "8f14e45f-ceea-467d-9c8e-2c1f3a9b4d20",
"profilesQueued": 1,
"videosQueued": 0,
"videosSkipped": 0
},
"queue": {
"orgQueuedItems": 12,
"estimatedMinutes": 6
},
"meta": {
"request_id": "req_tiktok001"
}
}
| Field | Description |
|---|---|
data.jobId | Identifier for this import job. |
data.profilesQueued | Number of profile URLs accepted for expansion. |
data.videosQueued | Number of single video URLs queued. Videos discovered by expanding a profile are not counted here. |
data.videosSkipped | Number of URLs skipped — either already imported into your organization, or duplicated within this request. |
queue.orgQueuedItems | Items currently queued for your organization across all imports. |
queue.estimatedMinutes | Estimated time until the current queue finishes processing. |
Tracking progress
A profile import can produce many content items over time, and there is no per-job status endpoint. Track progress two ways:-
callbackUrl— receive a per-itemcontent.importedcallback as each video finishes processing (see Callback lifecycle), then a single job-level callback when the whole import completes:{ "event": "tiktok_import.completed", "jobId": "8f14e45f-ceea-467d-9c8e-2c1f3a9b4d20", "profileUrl": "https://www.tiktok.com/@nasa", "summary": { "total": 50, "ready": 48, "errored": 2, "skipped": 0 }, "timestamp": "2026-03-10T14:45:00.000Z" } -
GET /v1/content/import/status— poll for your organization’s import queue and daily usage.queuedcounts imports still waiting, broken out by priority:queuedInteractivecounts items you sent withpriority: "high",queuedExpresscounts imports your team started from the ShortKit dashboard, andqueuedBulkcounts everything else.processing,readyToday,failedTodayanddailyUsedcount all content, including direct uploads.
Auto-populated metadata
ShortKit extracts and fills in the following fields automatically. You can change them after import using the Update Content endpoint.| Field | Value |
|---|---|
title | Video title (or first 200 chars of caption) |
description | Caption text (up to 2,000 chars) |
author | Creator username |
customMetadata | tiktok_id, tiktok_url, tiktok_likes, tiktok_views, tiktok_comments, tiktok_handle, tiktok_caption, tiktok_author_name (creator display name), tiktok_upload_date (YYYYMMDD), tiktok_timestamp (unix seconds), tiktok_sec_uid + tiktok_uploader_id (stable creator identifiers that survive handle changes), tiktok_avatar_url (creator avatar, re-hosted to a durable public URL), tiktok_follower_count, tiktok_following_count, tiktok_heart_count (total likes the creator has received), tiktok_video_count (creator-level counts — a point-in-time snapshot captured at import; rounded display values, e.g. 49600000), tiktok_is_ad + tiktok_is_ai (booleans — branded content and AI-generated flags) |