Skip to content
api API Updated 2026-03-28

Video Creation API

Use the Trending.com video creation API to build apps, start short-form video jobs, poll status, receive webhooks, and automate delivery.

Not technical? Have your chatbot set this up with you.

Copy this page into your AI assistant: https://trending.com/docs/api/video-creation-api

Then send: I’m new to APIs. Read this page and walk me through integration step by step.

One API page, all sections

Use the tabs below to jump to the section you need. This page combines all API guides in one place so you can hand one link to a developer, workflow builder, or AI agent.

If you are not writing code, start with One-click create or AI assistant setup instead.

If you are planning a focused mobile or web product, also read Build a Story video app. If you are running many channels or clients, read Automate videos for multiple channels.


Who this page is for

Use this page if you are building an app, automation, or AI agent. If you just want to make videos yourself, start with the dashboard or AI assistant setup instead.

Base URLs

  • Production: https://trending.com

Tools (in the dashboard)

Building agent workflows? See AI assistant setup for API-key MCP setup and everyday usage flow.

Keep API keys private. Anyone who gets your key can send requests as your account and spend your credits. If a key might be exposed, revoke it immediately and issue a new key.

On /dashboard/developer/api-keys, each key now shows quick 24h request/error stats, and revoke uses a confirmation step to help prevent accidental lockouts.

Create a video (API)

You must send an Idempotency-Key for create requests. This prevents accidental duplicate videos and duplicate charges on retries.

POST /api/v1/story/videos
Authorization: Bearer YOUR_API_KEY
Idempotency-Key: 123e4567-e89b-12d3-a456-426614174000
Content-Type: application/json

	{
	  "preview_only": false,
	  "prompt": "A short scary story about a hotel keycard.",
	  "webhook": {
	    "url": "https://example.com/webhooks/trending",
	    "secret": "optional shared secret",
	    "events": ["trending.story.v001.*"]
	  }
	}

Then either poll status or use webhooks:

  • Poll: GET /api/v1/story/videos/{video_id}
  • Story reuse sources: GET /api/v1/story/character-sources (discover valid reuse_characters_from_video_id + character-sheet links)
  • Story reference images: GET /api/v1/story/reference-images (discover valid asset_id values by name) and POST /api/v1/story/reference-images (upload jpg/png/webp, max 3MB, or send a direct HTTPS source_url)
  • Story outro (optional): GET /api/v1/story/videos/{video_id}/outro (inspect current outro config), PUT /api/v1/story/videos/{video_id}/outro (update line/speaker/reference/attachments), POST /api/v1/story/videos/{video_id}/outro/image (generate outro image)
  • Story slide animations (optional): GET /api/v1/story/videos/{video_id}/slide-animations to review per-slide costs/status, POST /api/v1/story/videos/{video_id}/slide-animations to animate one selected slide (lip-sync to that slide’s narration audio is on by default), DELETE /api/v1/story/videos/{video_id}/slide-animations/{slide_index} to discard one generated clip and keep the image
  • Render later: POST /api/v1/story/videos/{video_id}/render (send Idempotency-Key)
  • Abort run: POST /api/v1/story/videos/{video_id}/abort
  • Webhook: set webhook.url

Note: For Story ad/outro workflows, recommended sequence is preview-first: configure outro, generate outro image, optionally animate selected slides, then render.

Note: Story slide animation is optional, can be expensive, and is per-slide only (no animate-all endpoint). Always show the per-slide credit estimate and require explicit user approval before animating each slide. Current pricing is a fixed 10 credits per derived second. Narration lip-sync is enabled by default for each slide when narration audio exists, and Studio falls back quietly if Kling cannot use the slide audio clip. Kling runs at most 3 Story slide animations platform-wide, so extra requests remain queued until a slot opens.

Readiness note: If you are unsure whether the blocker is plan access, template access, or credits, call POST /api/v1/account/readiness before create. The same logic powers the hosted MCP readiness check.

Poll responses include progress fields like step_label, status_detail, progress_percent, eta_seconds, step_eta_seconds, pipeline_eta_seconds, Story timing details, plus dashboard_url/preview_url and control_urls for lifecycle actions.

Triggers (incoming webhook style)

If you want a single endpoint that can start any video type, use triggers:

  • API key trigger: POST /api/v1/triggers/videos
  • Signed trigger (recommended for third-parties): POST /api/v1/triggers/webhook

Signed triggers use X-Trending-Timestamp and X-Trending-Signature-V1 so you don’t need to share an API key. We also support an optional X-Trending-Nonce header for replay protection.

Full guide: Incoming Triggers

Webhooks (outbound)

Webhooks are how we notify you about progress/completion without polling. By default we only deliver terminal events: *.completed and *.failed.

To receive more events (queued/started/progress/render.*), set webhook.events as an allowlist (supports * wildcards). Story slide animation lifecycle also supports optional events: trending.story.v001.slide_animation.queued, trending.story.v001.slide_animation.in_progress, trending.story.v001.slide_animation.deleted, trending.story.v001.slide_animation.completed, trending.story.v001.slide_animation.failed.

Full guide: Webhooks

Logs & debugging

Quick integration test

For a quick integration check, send one preview-only create request with webhook.url, then verify both poll status and webhook delivery/signature validation.

Common errors

  • 401 missing_api_key — send Authorization: Bearer
  • 422 idempotency_required — send Idempotency-Key
  • 429 rate_limited — slow down; retry later
  • 402 insufficient_credits — add credits or reduce options
  • 403 membership_api_required — current plan does not include API access
  • 403 membership_mcp_required — MCP-identified request needs an MCP-enabled plan
  • 403 membership_template_required — selected template is not included in your current plan
  • 413 storage_quota_exceeded — upload exceeds remaining storage quota

What this is

The Trending.com API lets apps, automations, and AI agents create videos programmatically. Videos are generated in the background, so you start a video, then check status until it is ready.

If you are not coding, use the dashboard or MCP instead. This page is for builders.

Step 1 – Create an API key

  1. Log in to your account.
  2. Go to /dashboard/developer/api-keys.
  3. Click Create API key and copy the key (it is shown once).
  4. On that same page you can revoke keys and view recent request logs.

API key creation and API requests are membership-gated. If your plan does not include API access, requests return 403 membership_api_required.

Template access is also plan-gated. If your plan does not include the selected video type, create requests return 403 membership_template_required with allowed_templates and an upgrade_url.

Authentication

Send your API key as a Bearer token:

Authorization: Bearer YOUR_API_KEY

Normal API routes read API keys from headers only. JSON-body api_key support is limited to the Incoming Triggers endpoint below.

Using a webhook tool like Zapier/Make? You can also use our Incoming Triggers endpoint, which supports putting api_key in the JSON body — or using a signed trigger (no API key shared) created at /dashboard/developer/triggers.

Idempotency (recommended)

When you create a video, you must include an Idempotency-Key header. If you retry the same request, we will return the existing run instead of creating a duplicate.

Idempotency-Key: 123e4567-e89b-12d3-a456-426614174000

Credits pricing

Credits are charged per pipeline event. Current flat prices:

  • *.v001.script.generate = 3 credits
  • *.v001.render = 30 credits
  • *.v001.metadata.generate = 0 credits (included in render pricing)

Other event costs (for example slide images or narration units) are computed from the active pricing configuration.

Quote credits before create

Use this endpoint to estimate credits before calling create:

POST /api/v1/cost-estimate

Request body:

{
  "template": "story",
  "request": {
    "preview_only": false,
    "script": { "images_requested": 12 }
  }
}

Response includes estimate.total_credits plus a breakdown:

{
  "template": "story",
  "template_version": "v001",
  "estimate": {
    "preview_only": false,
    "slides": 12,
    "narration_units": 27,
    "script_credits": 3,
    "image_credits": 120,
    "narration_credits": 27,
    "transcription_credits": 3,
    "metadata_credits": 0,
    "render_credits": 30,
    "total_credits": 183,
    "pricing_version": "2026-03-19.public-pricing-estimate-v002",
    "pricing_source": "configured_pricing"
  }
}

This is an estimate only. Final charges are enforced by the ledger at hold/capture time.

When preview_only=true, the estimate intentionally sets render_credits to 0. Full renders still use the fixed 30 credit render event.

Check readiness before create

Use this endpoint when you want a quick answer to “can this account create this request right now?” before you spend time on create retries or support debugging.

POST /api/v1/account/readiness

Request body:

{
  "template": "rants",
  "request": {
    "preview_only": true,
    "prompt": "A rant about fake friends who only text when they are bored."
  }
}

Response includes account access plus template-specific blockers:

{
  "account": {
    "plan_code": "starter",
    "plan_label": "Starter",
    "api_access": false,
    "mcp_access": false,
    "available_credits": 50,
    "allowed_templates": ["rants"]
  },
  "template_readiness": {
    "template": "rants",
    "template_access": true,
    "required_credits": 54,
    "available_credits": 50,
    "credits_needed": 4,
    "ready_to_create": false
  }
}

Use this when you need a stable blocker answer like membership_api_required, membership_mcp_required, membership_template_required, or a credit shortfall before you call create.

How it works (simple)

  1. Start: POST to a create endpoint. You get a video_id immediately.
  2. Wait: either poll with GET, or provide a webhook URL.
  3. Done: when completed, the response includes the final render_url (if rendered). This is a short-lived signed download URL, not a permanent public CDN link.

When a render URL is present, responses also include retention metadata (retention + render.retention) so you can tell users when the link expires.

Treat render_url as a temporary file-delivery URL. Download the MP4 promptly if you need to keep it, and do not embed or hotlink it as if it were a stable CDN asset.

Auto title selection

Many video types generate social copy (3 title ideas + a description). We automatically pick the shortest generated title as the default social title.

If the video still has a draft placeholder name, we also promote that selected title to the video name. If you send your own name, your name is kept.

Endpoints

Each video type has:

  • POST /api/v1/{video_type}/videos — create/start pipeline
  • GET /api/v1/{video_type}/videos/{video_id} — poll pipeline status
  • POST /api/v1/{video_type}/videos/{video_id}/render — render later from a preview/draft (Idempotency-Key required)
  • POST /api/v1/{video_type}/videos/{video_id}/abort — cancel an active run
  • POST /api/v1/account/readiness — check plan/template/credit readiness before create
  • GET /api/v1/library/folders — list your folder IDs plus candidate summaries for non-Story background selection
  • POST /api/v1/library/folders/coverage — preflight folder sufficiency/auto-fill behavior before create
  • GET /api/v1/voice-actors — list selectable narrated voices (name, gender, age, accent, preview_url, favorite flags)
  • POST /api/v1/voice-actors/{voice_actor_id}/favorite — favorite/unfavorite a voice actor globally or per narrated template
  • GET /api/v1/story/character-sources — list reusable Story character-sheet sources for your account
  • GET /api/v1/story/reference-images — list saved Story reference images for your account (returns asset_id values)
  • GET /api/v1/story/outro-sources — list reusable Story outro sources for your account (returns key values you can reuse on create)
  • POST /api/v1/story/reference-images — upload a Story reference image by multipart form-data or by direct HTTPS source_url
  • GET /api/v1/story/videos/{video_id}/outro — read Story outro config (line/speaker/reference options)
  • PUT /api/v1/story/videos/{video_id}/outro — update Story outro config (line/speaker/reference/attachments)
  • POST /api/v1/story/videos/{video_id}/outro/image — generate Story outro image from current outro settings
  • GET /api/v1/story/videos/{video_id}/slide-animations — list optional Story per-slide animation options/status/costs
  • POST /api/v1/story/videos/{video_id}/slide-animations — animate one selected Story slide (timings-derived duration only, billed at 10 credits/second; lip-sync to that slide’s narration audio is on by default; queues automatically if all 3 platform-wide Kling slots are busy)
  • DELETE /api/v1/story/videos/{video_id}/slide-animations/{slide_index} — delete one Story slide animation and fall back to image

Template-specific create/poll endpoints:

  • Story: POST /api/v1/story/videos, GET /api/v1/story/videos/{video_id}, GET /api/v1/story/character-sources, GET /api/v1/story/reference-images, GET /api/v1/story/outro-sources, POST /api/v1/story/reference-images, GET /api/v1/story/videos/{video_id}/outro, PUT /api/v1/story/videos/{video_id}/outro, POST /api/v1/story/videos/{video_id}/outro/image, GET /api/v1/story/videos/{video_id}/slide-animations, POST /api/v1/story/videos/{video_id}/slide-animations, DELETE /api/v1/story/videos/{video_id}/slide-animations/{slide_index}
  • DM Story: POST /api/v1/dmstory/videos, GET /api/v1/dmstory/videos/{video_id}
  • Confession: POST /api/v1/confession/videos, GET /api/v1/confession/videos/{video_id}
  • Did You Know: POST /api/v1/didyouknow/videos, GET /api/v1/didyouknow/videos/{video_id}
  • Rants: POST /api/v1/rants/videos, GET /api/v1/rants/videos/{video_id}
  • Ranking: POST /api/v1/ranking/videos, GET /api/v1/ranking/videos/{video_id}
  • List: POST /api/v1/list/videos, GET /api/v1/list/videos/{video_id}, GET /api/v1/list/videos/{video_id}/script, PUT /api/v1/list/videos/{video_id}/script or PATCH /api/v1/list/videos/{video_id}/script, POST /api/v1/list/videos/{video_id}/script/ctas

Polling for status

Poll every 5 to 15 seconds. For render completion checks, expect several minutes under normal load.

GET /api/v1/{video_type}/videos/{video_id}

When completed, the response includes:

  • request_id (use this to find the request in /dashboard/developer/api-logs)
  • status (example: queued, running, completed, failed)
  • current_step (what step is happening now)
  • step_label, status_detail, step_number, total_steps, progress_percent
  • eta_seconds (legacy step ETA alias), step_eta_seconds, pipeline_eta_seconds, and agent_status_message (latest pipeline narration)
  • queue_wait_seconds, active_processing_seconds, step_elapsed_seconds, and timestamps (queue_started_at, processing_started_at, processing_finished_at, step_started_at)
  • timing for measured/average seconds (Story currently includes per-step + per-slide timing stats)
  • story_progress + long_running_notice for Story slide-heavy runs
  • assets for Story (character sheet URL, attached reference-image URLs, per-slide attachment plan, per-slide media URLs, assets.outro config/image state, optional slide-animation summary, and narration audio URL while the pipeline runs)
  • dashboard_url + preview_url + requires_auth_for_preview (login required to open the wizard preview)
  • control_urls.poll, control_urls.start_render, and control_urls.abort for lifecycle control
  • background_selection_summary on non-Story templates (folder-first counts plus fallback policy metadata)
  • render.render_url (short-lived signed download URL for the final video when rendered)
  • retention and render.retention (keep_days, expires_at, notice, admin_exempt) for download-window UX
  • warnings[] on non-Story templates (auto-fill/underfill/upload-profile notices for folder-first selection)
  • pipeline_error (only when failed; sanitized envelope with code + safe message, plus optional step/category/retryable/hint)

If your product needs a long-lived copy, download the file when it is ready and store your own reference. Do not rely on render.render_url as a permanent asset URL.

Webhooks (optional)

If you provide webhook.url in your create request, we send an HTTP POST when the video finishes or fails.

Full guide: Webhooks tab

Webhook options:

  • webhook.url — where to send the webhook
  • webhook.secret — optional secret to sign the payload
  • webhook.events — optional allowlist (supports * wildcards). If omitted, we only deliver terminal events (*.completed and *.failed).

Events look like:

  • trending.story.v001.completed, trending.story.v001.failed (default)
  • trending.story.v001.queued, trending.story.v001.started (optional)
  • trending.story.v001.progress (optional; step changes)
  • trending.story.v001.slide_animation.completed, trending.story.v001.slide_animation.failed (terminal events; optional for Story slide animation lifecycle)
  • trending.story.v001.slide_animation.queued, trending.story.v001.slide_animation.in_progress, trending.story.v001.slide_animation.deleted (optional; include in webhook.events to receive non-terminal animation updates)
  • trending.story.v001.outro_image.completed, trending.story.v001.outro_image.failed (terminal events; optional for Story outro-image lifecycle)
  • trending.story.v001.outro_image.queued (optional; include in webhook.events to receive non-terminal outro-image updates)
  • trending.list.v001.script.generated, trending.list.v001.script.updated (optional non-terminal List Step-2 lifecycle events; include in webhook.events allowlist)
  • trending.story.v001.render.started, trending.story.v001.render.completed, trending.story.v001.render.failed (optional)
  • …and the same pattern for the other video types.

Example allowlists:

{
  "webhook": {
    "events": [
      "trending.story.v001.*",
      "trending.*.v001.completed",
      "trending.*.v001.failed"
    ]
  }
}

Preview-only (optional)

If you set preview_only to true, we stop before the final render. The status becomes completed, but render.render_url stays null until you render the video later in the website.

Limits & rate limiting

  • Requests may be rate-limited (429) if you send too many create/poll calls too quickly.
  • Very large request bodies may be rejected (413).
  • Default API request-body cap is 3,145,728 bytes (3MB).
  • For current limits, see /dashboard/developer.

Create requests (by video type)

All create endpoints return immediately with a video_id. Rendering happens in the background.

Voice note for narrated auto-cast templates (story, dmstory, confession, didyouknow, rants, list): if you do not set explicit voice actor IDs (where supported), Studio may auto-pick from your saved favorites. Order: template favorites first, then global favorites, then normal defaults.

Use GET /api/v1/voice-actors to show users voice choices by name, gender, age, accent, and preview_url. Use POST /api/v1/voice-actors/{voice_actor_id}/favorite to favorite/unfavorite globally or for a specific narrated template.

Music note for narrated templates (story, dmstory, confession, didyouknow, rants, list): send a music object to control background music. Use music.enabled to turn it on, optional music.music_id (or music.id) for a specific track, optional music.category for mood, and optional music.volume_percent (default 20). If no track ID is set, Studio tries favorite tracks that match mood first, then a random accessible track.

Background folder note for non-Story templates (dmstory, confession, didyouknow, rants, ranking, list): you can send background.folder_id to prefer a Library folder. Studio picks folder clips first, then auto-fills from the template fallback pool when needed.

  • Folder ID must belong to your account, or create returns 422 invalid_background_folder_id.
  • Folder entries can include your uploads and shared library:{id} items you added to that folder.
  • Fallback scope: narrated templates + List use approved shared ASMR clips; Ranking uses approved category-based clips.
  • If folder underfill strategy is set to error, runs fail with pipeline_error.code=background_folder_underfilled instead of auto-filling.
  • Create + poll responses include background_selection_summary and warnings[] so clients can inspect folder-first counts, fallback policy, and auto-fill notices.

Library folder helper endpoints

  • GET /api/v1/library/folders — returns caller-owned folders with counts for user/shared/music/image items, upload-profile counts, and candidate summaries for background/ranking scope.
  • POST /api/v1/library/folders/coverage — accepts template + optional folder_id, required_count, required_total_duration_seconds, and ranking_category_preset. Response includes eligibility counts/duration, prediction, and warnings[].
  • POST /api/v1/library/videos — upload one MP4/MOV/WebM/M4V clip (max 200MB), optionally add it to a caller-owned folder, and queue downloader processing. Optional fields: name, folder_id, upload_profile (background or ranking). Local uploads are media-probed before they are accepted and must include a readable video stream, be at least 500px wide, and be at least 10 seconds long.
  • POST /api/v1/library/video-urls — import one direct HTTPS MP4/MOV/WebM/M4V file URL, optionally add it to a caller-owned folder, and queue downloader processing. Optional fields: name, folder_id, upload_profile. Social post URLs and generic webpages are not supported. URL imports are downloader-validated before the asset becomes available and use dedicated upload throttles in addition to normal API auth checks.
  • GET /api/v1/library/uploads/{asset_id} — poll the latest status for one caller-owned Library upload/import asset. Returns asset.status, readiness, final/preview URLs when available, batch counters/status when known, source metadata (source_type, source_host, original file name), media facts such as duration, resolution, size, audio presence, codecs, and bitrate, plus structured upload warnings[] and retained-source details when Studio kept a short-lived archive copy of a local upload. While the asset is still processing, the response also includes poll_after_seconds.

Coverage preflight returns 422 invalid_background_folder_id when folder_id is not owned by the caller. For ranking, invalid preset keys return 422 invalid_ranking_category_preset. Library video upload/import returns 422 invalid_library_folder_id when folder_id is not owned by the caller and 413 storage_quota_exceeded when the upload exceeds remaining storage. Local multipart uploads can also return 422 invalid_library_video_upload when the uploaded file fails media-probe validation.

Use POST /api/v1/library/videos when the file lives on the caller machine and you need to send bytes directly to Studio. Use POST /api/v1/library/video-urls when you already have a direct HTTPS file URL. Hosted MCP local-file helpers use MCP-runtime file paths, so they are not a substitute for browser/API multipart upload of a caller-local file.

After either upload/import path, poll GET /api/v1/library/uploads/{asset_id} until asset.status becomes available or failed. A queued upload is not ready to use in a Library folder-backed create request until it reaches available.

Create a Story video

POST /api/v1/story/videos

Request body (all fields optional):

{
  "name": "Optional video name",
  "prompt": "Optional story idea (min 6 chars if provided)",
  "preview_only": false,
	  "script": {
	    "images_requested": 12,
	    "voice_format": "dialogue",
	    "narrator_enabled": false,
	    "visual_style_preset": "semi_realistic_painterly",
	    "visual_style_custom": null,
	    "reuse_characters_from_video_id": null,
	    "reuse_story_mode": "different",
	    "reference_images": [
	      { "asset_id": 501, "alias": "logo_ref", "description": "CPALead logo mark" },
	      { "story_id": 456, "run_id": 9912, "alias": "mascot_sheet", "description": "Mascot style reference" }
	    ],
	    "reference_instruction": "Use @logo_ref on the jersey chest only.",
        "outro": {
          "enabled": true,
          "text": "This story was brought to you by SkyLaunch.",
          "speaker": "Ava"
        }
	  },
      "music": {
        "enabled": true,
        "category": "Dramatic",
        "volume_percent": 20
      },
		  "webhook": {
		    "url": "https://example.com/webhooks/trending",
		    "secret": "optional shared secret",
		    "events": ["trending.story.v001.completed", "trending.story.v001.failed"]
	  }
	}

Parameter mapping (Story)

  • name (string, max 180) — optional video name for your library.
  • prompt (string, 6 to 1000 chars) — optional story idea. If omitted, we pick a prompt for you.
  • preview_only (boolean) — if true, stops before the final render so you can render later.
  • script.images_requested (integer) — how many slides to generate (12 by default). Allowed range is 10 to 30 for normal runs, or 2 to 30 when preview_only=true.
  • script.voice_format (string enum) — dialogue, monologue, or narrated.
  • If explicit voice IDs are not sent, Story may auto-use favorites using script gender/age when available. Story also avoids using the same voice for two different characters when an alternate match exists.
  • Caption defaults are auto-applied during creation. Per-character presets are seeded by speaker order: first 24, second 33, third 36, fourth 41 (additional speakers fall back to the base Story preset).
  • script.narrator_enabled (boolean) — only used when voice_format=dialogue.
  • script.visual_style_preset (string enum) — one of the presets below, or custom.
  • script.visual_style_custom (string, max 240) — only used when visual_style_preset=custom.
  • script.reuse_characters_from_video_id (integer) — optional. Use characters from one of your past Story videos. Call GET /api/v1/story/character-sources to discover valid IDs and character-sheet URLs.
  • script.reuse_story_mode (string enum) — different (default), similar (standalone new episode, not continuation), or continuation. If you use similar or continuation, you must also set reuse_characters_from_video_id.
  • script.reference_images (array, max 9) — optional Story references. Each entry must be either {asset_id, alias?, description?} (saved reference image) or {story_id, run_id, alias?, description?} (previous Story run image you own). description is optional (max 220) and recommended when a filename is ambiguous.
  • script.reference_instruction (string, max 600) — optional extra instructions for how to use the attached references.
  • script.outro (optional object) — set enabled, text (max 900), and speaker (max 120 or auto) to define the outro/ad line during script creation.
  • script.outro.source_key (string, optional) — reuse a saved Story outro by passing a key from GET /api/v1/story/outro-sources.
  • Story script output plans where references are used: character_sheet_reference_aliases + slide_prompts[].reference_aliases. Slide generation then attaches only aliases planned for that slide.
  • @alias tokens are visual-only and should appear in visual prompts only; spoken fields remain human-readable (no handle/file-name speech).
  • Slide prompts for slides 2..N include explicit prior-scene continuity guidance (what persists from the previous slide) unless intentionally changed.
  • Continuity behavior (default on): Story slide generation is ordered. If slide N-1 is missing or failed, slide N waits instead of generating out of order.

Story reference image helper endpoints

  • GET /api/v1/story/reference-images — returns saved reference images for your account, including asset_id, name, image_url, and an alias_suggestion (sorted newest first).
  • POST /api/v1/story/reference-images — upload one image (jpg/jpeg/png/webp, max 3MB) by multipart form-data, or send a direct HTTPS source_url, and receive a new asset_id. Uploads also enforce remaining storage quota; if exceeded you receive 413 storage_quota_exceeded. Uploading reference images does not consume credits.

Story outro helper endpoints

  • GET /api/v1/story/outro-sources — returns reusable saved outro sources for your account. Use the returned key as script.outro.source_key on create.
  • GET /api/v1/story/videos/{video_id}/outro — returns current outro settings, speaker options, reference slide options, selected outro image state, and image-generation estimate fields.
  • PUT /api/v1/story/videos/{video_id}/outro — updates outro line/speaker + optional image prompt/reference slide/attachment asset IDs (max 4 attachments).
  • POST /api/v1/story/videos/{video_id}/outro/image — generates the outro image from character sheet + selected reference slide + optional attachment assets, returning run_id + charged credits metadata.
  • Attachment IDs come from existing Story reference-image assets (same asset_id values returned by Story reference-image helper endpoints).
  • Recommended flow: create with preview_only=true, configure/generate outro media, optionally animate selected slides, then call render.

Visual style presets (Story)

  • semi_realistic_painterly (default)
  • anime
  • 3d_animated
  • photorealistic
  • cel_shaded_cartoon
  • comic
  • watercolor_storybook
  • flat_vector
  • pencil_sketch
  • oil_painting
  • neon_cyberpunk
  • noir
  • claymation
  • knitted_yarn
  • low_poly_3d
  • custom

What “custom” means (Story)

If you set script.visual_style_preset to custom, set script.visual_style_custom to your own style prompt.

{
  "script": {
    "visual_style_preset": "custom",
    "visual_style_custom": "semi-realistic painterly illustration, warm rim light, soft shadows, cinematic depth of field"
  }
}

Create a DM Story video

POST /api/v1/dmstory/videos

Main options:

  • prompt (min 6 chars if provided)
  • script.topic_preset (optional) — one of: accidental_text, caught_in_a_lie, best_friend_drama, group_chat_exposed, boss_text, mysterious_number, receipt_drop, family_drama, roommate_drama, petty_argument
  • voice.speed_percentage (80–200) and voice.remove_silences (boolean)
  • background.folder_id (integer, optional) — pick from your folder first, then fallback to approved shared ASMR clips.
  • music (optional) — enabled, optional music_id/id, optional category, optional volume_percent (default 20)
  • If explicit voice IDs are not sent, DM Story may auto-use favorites by speaker gender and keeps the two speakers on different voices when possible.
  • preview_only (boolean)

Create a Confession video

POST /api/v1/confession/videos

Main options:

  • prompt (min 6 chars if provided)
  • script.topic_preset (optional) — one of: embarrassing, school, friend_drama, relationship, family, work, money, petty, secret, mistake
  • voice.speed_percentage (80–200) and voice.remove_silences (boolean)
  • background.folder_id (integer, optional) — pick from your folder first, then fallback to approved shared ASMR clips.
  • music (optional) — enabled, optional music_id/id, optional category, optional volume_percent (default 20)
  • If explicit voice IDs are not sent, Confession may auto-use favorites and match against confessor_gender when available.
  • preview_only (boolean)

Create a Did You Know video

POST /api/v1/didyouknow/videos

Main options:

  • prompt (min 6 chars if provided)
  • script.topic_preset (optional) — one of: animals, food, space, history, science, everyday
  • background.exit_enabled (boolean), background.exit_video_id (integer), and optional background.folder_id (integer)
  • voice.speed_percentage (80–200) and voice.remove_silences (boolean)
  • music (optional) — enabled, optional music_id/id, optional category, optional volume_percent (default 20)
  • If explicit voice IDs are not sent, Did You Know may auto-use favorites (no gender/age filter required).
  • preview_only (boolean)

Create a Rants video

POST /api/v1/rants/videos

Main options:

  • prompt (min 6 chars if provided)
  • script.topic_preset (optional) — one of: wholesome, school, friend_drama, relationship, fake_friend, petty_revenge, rich_kid, glow_up, scammer, strict_parents
  • voice.speed_percentage (80–200) and voice.remove_silences (boolean)
  • background.folder_id (integer, optional) — pick from your folder first, then fallback to approved shared ASMR clips.
  • music (optional) — enabled, optional music_id/id, optional category, optional volume_percent (default 20)
  • If explicit voice IDs are not sent, Rants may auto-use favorites and match against ranter_gender when available.
  • preview_only (boolean)

Create a Ranking video

POST /api/v1/ranking/videos

Main options:

  • prompt (min 6 chars if provided)
  • ranking.category_preset (optional) — one of: bodycam, security_camera, cinematic, animals, cartoon_characters, celebrities, vehicles, funny, cursed, absurd, wholesome, epic, chase, transformation, disaster, asmr
  • background.folder_id (integer, optional) — pick from your folder first, then fallback to approved category-based clips.
  • preview_only (boolean)

Create a List video

POST /api/v1/list/videos

Main options:

  • prompt (min 2 chars if provided) — if omitted, we pick a topic.
  • script.item_count (1–15)
  • script.include_ai_ctas (boolean, optional, default true) — set false to disable AI-generated CTA lines.
  • Generated list script lines may include bracketed emotion/sound cues for narration (for example [laughs], [whispers], [crowd gasps]). Treat these as audio/performance cues, not visual actions, and avoid [PAUSE] tags.
  • script.category (optional) — facts, tips, jokes, trivia, myths, and more
  • script.subcategory (optional) — only valid when a category is selected
  • voice.voice_actor_id (integer, optional; fallback default is 233)
  • background.folder_id (integer, optional) — pick from your folder first, then fallback to approved shared ASMR clips.
  • music (optional) — enabled, optional music_id/id, optional category, optional volume_percent (default 20)
  • If voice.voice_actor_id is omitted, List may auto-use favorites before default fallback voice selection.
  • Background clips: auto-selected from our ASMR clip library (hook + each item)
  • Caption style defaults are auto-applied: preset 24 (Hyperlegible), single-line captions with auto-shrink starting around 18 visible characters, font_scale=215%, glow effect, emoji size 250 with pop + shake, blurred_background=false, force_portrait_backgrounds=true.
  • preview_only (boolean)

List script editing endpoints (Step 2 parity)

  • GET /api/v1/list/videos/{video_id}/script — fetch editable intro + ordered lines + CTA metadata + script.ctas[] + script.emoji_suggestions[] + script.updated_at.
  • PUT /api/v1/list/videos/{video_id}/script or PATCH /api/v1/list/videos/{video_id}/script — save full ordered script edits (line move/delete/edit, intro edits, CTA line type/source, optional cta_meta, optional emoji_suggestions).
  • POST /api/v1/list/videos/{video_id}/script/ctas — CTA helper endpoint with mode=suggest|apply, placement controls, and optional replace-existing behavior.
  • Optimistic lock support: send expected_updated_at in write payloads (or send If-Match with prior ETag). Stale writes return 409 script_version_conflict.
  • Script responses include ETag when script.updated_at is available.
  • When narration is attached, updates require reset_audio=true; otherwise API returns 409 audio_locked.
  • While a pipeline run is queued or running, updates return 409 pipeline_run_active.

What this is

This is a single “webhook-style” endpoint you can POST to in order to start a video. It is useful for automation tools (Zapier, Make, custom webhooks) that prefer one URL and simple JSON.

This is different from outbound webhooks (where we notify you when the video completes).

Endpoints

You have two options:

POST /api/v1/triggers/videos   (API key)
POST /api/v1/triggers/webhook  (signed trigger, no API key)

Option A – API key triggers

Use this when you control the system sending requests and can safely store an API key. (Create keys at /dashboard/developer/api-keys.)

Authentication supports 3 ways:

  • Recommended: Authorization: Bearer YOUR_API_KEY
  • X-Trending-Api-Key: YOUR_API_KEY
  • api_key in the JSON body (helpful for webhook tools; supported on this trigger endpoint only)

Option B – Signed triggers (no API key)

Use this when you don’t want to share an API key with a third-party tool. Create a signed trigger at /dashboard/developer/triggers to get a trigger_id + secret.

Send these headers:

  • X-Trending-Trigger — your trigger_id
  • X-Trending-Timestamp — Unix seconds (string). Requests must be within ~5 minutes.
  • X-Trending-Signature-V1: sha256=... — HMAC of {timestamp}.{raw_body} using the trigger secret
  • X-Trending-Nonce — optional but recommended unique nonce (replay protection)

Signature pseudo:

expected = "sha256=" + HMAC_SHA256(timestamp + "." + raw_body, trigger_secret)

Required fields

  • video_type — one of: story, dmstory, confession, didyouknow, rants, ranking, list

All other fields are the same as that video type’s normal create endpoint.

Idempotency (required)

Triggers require idempotency so retries don’t create duplicates. If your tool can set headers, send Idempotency-Key. If it cannot, you can send idempotency_key in the JSON body and we will treat it like the header.

{
  "video_type": "story",
  "idempotency_key": "123e4567-e89b-12d3-a456-426614174000"
}

Example (API key in JSON body)

POST /api/v1/triggers/videos
Content-Type: application/json

{
  "api_key": "YOUR_API_KEY",
  "video_type": "story",
  "idempotency_key": "123e4567-e89b-12d3-a456-426614174000",
	  "preview_only": false,
	  "prompt": "A short scary story about a hotel keycard.",
	  "webhook": {
	    "url": "https://example.com/webhooks/trending",
	    "secret": "optional shared secret",
	    "events": ["trending.story.v001.completed", "trending.story.v001.failed"]
	  }
	}

Example (signed trigger)

# Bash example (requires openssl)
TRIGGER_ID="YOUR_TRIGGER_ID"
SECRET="YOUR_TRIGGER_SECRET"
TS="$(date +%s)"
NONCE="$(uuidgen 2>/dev/null || cat /proc/sys/kernel/random/uuid)"
BODY='{"video_type":"story","idempotency_key":"123e4567-e89b-12d3-a456-426614174000","preview_only":false,"prompt":"A short scary story about a hotel keycard."}'

SIG_HEX="$(printf "%s.%s" "$TS" "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | awk '{print $2}')"

	curl -X POST "https://trending.com/api/v1/triggers/webhook" \
	  -H "Content-Type: application/json" \
	  -H "X-Trending-Trigger: $TRIGGER_ID" \
	  -H "X-Trending-Timestamp: $TS" \
	  -H "X-Trending-Signature-V1: sha256=$SIG_HEX" \
	  -H "X-Trending-Nonce: $NONCE" \
	  -d "$BODY"

Response

The response matches the normal create endpoint for that video type:

{
  "video_id": 12345,
  "pipeline_run_id": "6fba1c20-6d1c-4bb2-9a91-2c9f7fcb2d7c",
  "status": "queued",
  "poll_url": "/api/v1/story/videos/12345",
  "webhook": { "enabled": true, "url": "https://example.com/...", "events": ["..."] }
}

Notes

  • Use HTTPS in production and keep your API key / trigger secrets safe.
  • If your webhook tool can add headers, prefer Authorization: Bearer (standard).
  • Signed triggers are the safest option when you don’t want a third-party to store an API key.
  • Optional: restrict a trigger by IP CIDRs in /dashboard/developer/triggers.
  • To receive completion notifications, configure webhook.url and follow the Webhooks tab.

What webhooks do

If you provide webhook.url in your create request, we send an HTTP POST when your video finishes (*.completed) or fails (*.failed). Webhooks are designed for automation: update your app, notify users, or start downstream processing without polling.

You can manage endpoints, rotate secrets, and view delivery logs at /dashboard/developer/webhooks.

Quickstart

  1. Create a webhook endpoint in your app that accepts JSON.
  2. Return a 2xx response quickly (do heavy work asynchronously).
  3. (Recommended) Add a webhook.secret and verify the signature.
  4. De-dupe deliveries using the X-Trending-Delivery header (at-least-once delivery).

Webhook URL rules: use a public https:// endpoint on standard port 443 in production. We reject loopback, private-network, metadata, .local, and .internal hosts. For local/dev smoke runs, HTTP callbacks back to the configured Studio dev host are still allowed.

Headers

Every webhook request includes:

  • Content-Type: application/json
  • User-Agent: Trending-Webhooks/1.0
  • X-Trending-Delivery — unique delivery id (use this to de-dupe)
  • X-Trending-Event — the event name

If you set webhook.secret, we also include:

  • X-Trending-Timestamp — Unix seconds (string)
  • X-Trending-Signature-V1: sha256=... — HMAC of {timestamp}.{raw_body}
  • X-Trending-Signature: sha256=... — legacy (body-only)

Signature verification (recommended)

We sign each request using your webhook.secret. Always compute the signature on the exact raw bytes you received (do not re-encode JSON).

// PHP (Laravel) example
$raw = $request->getContent();
$timestamp = (string) $request->header('X-Trending-Timestamp', '');
$secret = 'replace-with-your-webhook-secret';
$expected = 'sha256=' . hash_hmac('sha256', $timestamp . '.' . $raw, $secret);
$received = (string) $request->header('X-Trending-Signature-V1', '');

if (!hash_equals($expected, $received)) {
    abort(401);
}

To reduce replay risk, reject old timestamps (example: older than 5 minutes):

// Optional replay window check
$ts = (int) $timestamp;
if ($ts <= 0 || abs(time() - $ts) > 300) {
    abort(401);
}

If you rotate secrets, accept both old and new secrets for a short overlap window to avoid dropped deliveries.

Event names

By default we deliver terminal events only:

trending.{video_type}.v001.completed
	trending.{video_type}.v001.failed

You can opt into additional events by setting webhook.events in the create request (or by setting an event allowlist on the saved endpoint in Webhook Tools). Allowlists support * wildcards.

trending.{video_type}.v001.queued
	trending.{video_type}.v001.started
	trending.{video_type}.v001.progress
	trending.{video_type}.v001.render.started
	trending.{video_type}.v001.render.completed
	trending.{video_type}.v001.render.failed

Allowlist examples:

trending.story.v001.*
	trending.*.v001.completed
	trending.*.v001.render.*

Supported video_type values (v001):

  • story, dmstory, confession, didyouknow
  • rants, ranking, list

Payload schema

Webhook payloads are JSON with a stable, compact shape:

{
  "event": "trending.story.v001.completed",
  "pipeline_run_id": "6fba1c20-6d1c-4bb2-9a91-2c9f7fcb2d7c",
  "video_id": 12345,
  "template": "story",
  "template_version": "v001",
  "status": "completed",
  "preview_only": false,
  "render_url": "https://trending.com/studio/renders/123/download?signature=...",
  "occurred_at": "2026-01-27T03:12:45Z",
  "error": null
}

Some events include extra fields:

  • *.progressfrom_step, to_step
  • *.render.*render_job_id, render_job_status

On failures, error is present and render_url may be null. Successful payloads use a short-lived signed download URL.

Download the file promptly if you need to keep or distribute it. Do not treat webhook render_url as a permanent public asset or hotlink target.

{
	  "event": "trending.list.v001.failed",
	  "pipeline_run_id": "6fba1c20-6d1c-4bb2-9a91-2c9f7fcb2d7c",
	  "video_id": 12345,
	  "template": "list",
  "template_version": "v001",
  "status": "failed",
  "preview_only": true,
  "render_url": null,
  "occurred_at": "2026-01-27T03:12:45Z",
  "error": {
    "code": "render_failed",
    "message": "Render failed due to ...",
    "step": "render"
  }
}

Delivery semantics (important)

  • At-least-once delivery: you may receive duplicates (retry). De-dupe by X-Trending-Delivery.
  • Fast acknowledgement: respond 2xx quickly; enqueue processing on your side.
  • Don’t trust IP allowlists: use signatures instead (safer across infra changes).

Retries

If your endpoint times out or returns a non-2xx response, we retry with exponential-ish backoff (up to 10 attempts):

10s, 30s, 2m, 10m, 30m, 1h, 2h, 4h (then continues until max attempts)

To stop retries, return a 2xx.

You can optionally override max attempts per endpoint in Webhook Tools (advanced).

Local testing

Two easy ways to test:

  1. Use Webhook Tools: visit /dashboard/developer/webhooks to send a signed test webhook.
  2. Expose localhost: use a tunnel like ngrok and set webhook.url to the public HTTPS URL.
# Example (ngrok)
ngrok http 3000

	# Set webhook.url to the https URL ngrok prints:
	# https://xxxx.ngrok-free.app/webhooks/trending

Integration smoke testing

For end-to-end verification, run one preview-only create request with a real webhook.url, then confirm your receiver gets a terminal event and signature headers validate.

Delivery logs

You can view recent deliveries and resend them from /dashboard/developer/webhooks. If a delivery is stuck in retrying and next_attempt_at is already in the past, resend from Webhook Tools or contact support.

Webhook Tools also supports exporting delivery logs (CSV and JSON) for auditing and debugging. You can filter by video_id and pipeline_run_uuid in the UI.


Never share secret keys

Trending.com will never include internal service keys in API responses or webhook payloads. If you ever see sensitive data, treat it as a bug and rotate secrets immediately.

Outbound webhooks: verify signatures

If you set webhook.secret, we include: X-Trending-Timestamp and X-Trending-Signature-V1. Always compute HMAC on the raw request body bytes you received.

Recommended: reject timestamps older than ~5 minutes to reduce replay risk.

Guide: Webhooks

Signed triggers: avoid sharing API keys

If you’re integrating with a third-party automation tool (Zapier/Make/custom webhook sender), use signed triggers so you don’t need to store an API key in that system.

Replay protection (recommended): X-Trending-Nonce

Signed triggers support an optional X-Trending-Nonce header. When you send a nonce, we reject duplicates (replays) for that trigger.

Guide: Incoming Triggers

Optional IP allowlists

You can optionally restrict signed triggers to IP CIDRs in the Triggers dashboard. This is extra protection in addition to signatures.

Dashboard password reset abuse protection

Studio password reset requests are protected with layered throttling and privacy-safe responses. We return the same confirmation message whether an account exists or not, and apply rate limits plus short cooldown windows to reduce automated abuse.

Dashboard email verification

Studio account registration sends a verification email and requires verified email for authenticated dashboard access. This helps reduce fake-signup abuse and keeps lifecycle sends tied to real inbox ownership.

SES bounce and complaint suppression

Studio now ingests SES SNS feedback events for bounces and complaints and adds those addresses to a local suppression list. Future emails are skipped for suppressed recipients to protect sender reputation.

Email preference categories

Lifecycle emails (welcome and retention) honor per-category unsubscribe links. Render-complete product emails can also be toggled from account settings. Security/account-access emails remain separate transactional flows.

Authenticated Studio users can manage these preferences directly from /dashboard/settings.

Secret rotation


Where to look first

Use request_id to correlate everything

Most API responses include a request_id. Copy it into /dashboard/developer/api-logs to find the exact request (including a redacted request/response body preview).

Common API errors

Errors are returned as:

{
  "request_id": "…",
  "error": {
    "code": "…",
    "message": "…",
    "details": { }
  }
}

401 – Authentication

  • missing_api_key — send Authorization: Bearer YOUR_API_KEY (or X-Trending-Api-Key / X-Api-Key). JSON-body api_key is only accepted on POST /api/v1/triggers/videos.
  • invalid_api_key — key is wrong/revoked; rotate at /dashboard/developer/api-keys.
  • invalid_signature — for signed triggers, recompute HMAC on the raw body bytes exactly as received.
  • nonce_reused — you reused X-Trending-Nonce; generate a new nonce per request.

422 – Validation / missing required headers

  • idempotency_required — send Idempotency-Key for create/trigger requests.
  • validation_failed — check error.details for field-level messages.

402 – Credits

  • insufficient_credits — add credits or lower requested options.
  • Subscription credits roll over with per-grant expiry windows (monthly grants: +2 months; annual grants: term end +2 months).
  • Upgrades are applied immediately; downgrades are applied on the next successful renewal payment.
  • Cancelled subscriptions remain active until term end, and payment-failed subscriptions do not retroactively claw back already-granted credits; failed renewals simply stop new cycle grants until payment succeeds.
  • Use /dashboard/videos/{video_id}/charges to see a simple credit breakdown for one video, including credits set aside, used, or given back.
  • Export your full ledger at /dashboard/billing/export/csv.
  • Use /dashboard/billing/statements to choose period ranges, then export at /dashboard/billing/statement/export/csv or /dashboard/billing/statement/export/pdf.
  • Promo and partner grants, including CPAlead credits after account verification, appear in the same billing ledger once they are posted.
  • Internal expiry adjustments are tracked for accounting but hidden from user billing and statement exports.

403 – Membership gates

  • membership_api_required — current plan does not include API access.
  • membership_mcp_required — MCP-identified requests require an MCP-enabled plan.
  • membership_template_required — selected template is not included in the current plan; check error.details.allowed_templates.

429 – Rate limits

  • rate_limited — slow down and retry later. Limits apply per API key / per trigger (not IP-only).

413 – Request too large

  • request_too_large — reduce payload size (or send fewer nested settings at once).
  • Default API request-body cap is 3,145,728 bytes (3MB).
  • Story reference-image uploads also enforce a per-file cap of 3MB.
  • Multipart uploads include a small protocol-overhead allowance, so an actual 3MB image file can still pass.

503 – Temporary service issue

  • service_unavailable — temporary outage or maintenance. Wait a minute and retry.
  • When final renders are paused for maintenance, preview-only work can still remain available. Check the response details for scope=final_render_only and preview_only_available=true.
  • If it continues, contact support and include your request_id.

Temporary generation-service load (Story image steps)

  • If Story image generation is under heavy load, polling can fail with pipeline_error.code such as slide_generation_failed or character_sheet_generation_failed.
  • Messages are intentionally sanitized for safety across all templates. Raw internal diagnostic payloads are not returned in API, webhook, or MCP responses.
  • For Story one-click, each slide now has a fail-fast cap of 2 total attempts (initial + one retry) before the pipeline stops.

Webhook deliveries: retries & timeouts

Webhooks can fail for many reasons: your server returned a 500, TLS/DNS errors, or slow response times. Trending.com retries deliveries automatically and records each attempt.

  • View attempts at /dashboard/developer/webhooks
  • Keep your receiver fast: respond 2xx quickly, then process async.
  • If you set webhook.secret, always verify signatures before trusting the body.

If a delivery stays in retrying but next_attempt_at is already in the past, trigger a resend from Webhook Tools or contact support.

If your receiver expects signatures but deliveries show as unsigned, set a webhook.secret on the request or endpoint.

Signed triggers: common pitfalls

  • Clock drift: ensure your sender’s clock is accurate (timestamps outside the allowed window are rejected).
  • Wrong body bytes: HMAC must be computed on the exact raw JSON sent (including whitespace).
  • IP allowlists: if you configured CIDRs, requests from other IPs will be rejected.

Next: Security and Webhooks.