Skip to content

feat: add POST /api/video/render endpoint#222

Open
sidneyswift wants to merge 1 commit intotestfrom
feature/video-render-endpoint
Open

feat: add POST /api/video/render endpoint#222
sidneyswift wants to merge 1 commit intotestfrom
feature/video-render-endpoint

Conversation

@sidneyswift
Copy link
Contributor

@sidneyswift sidneyswift commented Feb 13, 2026

Summary

Adds a POST /api/video/render endpoint that triggers a server-side video render as a Trigger.dev background task. Returns a run ID that can be polled via the existing GET /api/tasks/runs endpoint.

What Changed

New Files (10 files, 686 lines)

File Purpose
app/api/video/render/route.ts Route handler (POST + OPTIONS)
lib/render/renderVideoHandler.ts Handler: auth → validate → trigger → respond
lib/render/validateRenderVideoBody.ts Zod schema + validation function
lib/trigger/triggerRenderVideo.ts Triggers render-video task on Trigger.dev
lib/mcp/tools/render/registerRenderVideoTool.ts MCP tool sharing triggerRenderVideo
lib/mcp/tools/render/index.ts Render tool registration group

Modified Files

File Change
lib/mcp/tools/index.ts Register render tools

Tests (18 passing)

File Tests
lib/render/__tests__/validateRenderVideoBody.test.ts 11 tests (valid inputs, defaults, error cases)
lib/render/__tests__/renderVideoHandler.test.ts 5 tests (auth, validation, trigger, error)
lib/trigger/__tests__/triggerRenderVideo.test.ts 2 tests (trigger payload, handle return)

Architecture

Follows the exact same pattern as POST /api/sandboxes/setup:

POST /api/video/render
  → validateAuthContext (x-api-key or Bearer)
  → validateRenderVideoBody (Zod)
  → triggerRenderVideo (Trigger.dev)
  → { status: "processing", runId }

GET /api/tasks/runs?runId=xxx  ← existing endpoint
  → { status: "complete", data: { videoUrl } }

DRY

The MCP tool (render_video) and REST endpoint both call the same triggerRenderVideo function.

Next Steps (PR 2)

  • Trigger.dev renderVideoTask implementation (in tasks repo)
  • Remotion SocialPost composition added to remotion submodule
  • Upload to Supabase storage after render

Related

  • Docs PR: Added POST /api/video/render to OpenAPI spec + Mintlify docs

Summary by CodeRabbit

  • New Features
    • Added a video rendering API with support for customizable video parameters (resolution, frame rate, duration, codec).
    • Integrated authentication for secure render requests via API keys and Bearer tokens.
    • Enabled background video processing with run ID tracking for monitoring render jobs.

- Route handler at app/api/video/render/route.ts
- Zod validation for compositionId, inputProps, output settings
- Trigger function that fires render-video task on Trigger.dev
- Handler follows sandbox setup pattern: trigger → return runId
- MCP tool (render_video) shares triggerRenderVideo logic with API
- 18 tests covering validation, handler, and trigger function

Polling for render status uses existing GET /api/tasks/runs endpoint.
@vercel
Copy link
Contributor

vercel bot commented Feb 13, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
recoup-api Ready Ready Preview Feb 13, 2026 5:28pm

Request Review

@coderabbitai
Copy link

coderabbitai bot commented Feb 13, 2026

📝 Walkthrough

Walkthrough

This PR introduces a complete video rendering feature spanning API routes, request validation, authentication, and MCP tool integration. It adds a POST /api/video/render endpoint with CORS support, integrates Trigger.dev for background render tasks, registers an MCP tool for render capability, and includes Zod-based request validation with API key/Bearer token authentication.

Changes

Cohort / File(s) Summary
API Route & Handler
app/api/video/render/route.ts, lib/render/renderVideoHandler.ts
New API endpoint with OPTIONS CORS preflight and POST handler that orchestrates authentication, body validation, and render task triggering. Returns 200 with runId on success or appropriate error responses.
Request Validation
lib/render/validateRenderVideoBody.ts
Zod schema defining renderVideoBodySchema with required compositionId and sensible defaults (width=720, height=1280, fps=30, etc.). Validates incoming request bodies and returns structured error responses on failure.
Trigger.dev Integration
lib/trigger/triggerRenderVideo.ts
New module defining RenderVideoPayload type and triggerRenderVideo function to dispatch render tasks to Trigger.dev with composition metadata, dimensions, codec, and account context.
MCP Tools Registration
lib/mcp/tools/index.ts, lib/mcp/tools/render/index.ts, lib/mcp/tools/render/registerRenderVideoTool.ts
MCP tool pipeline augmented with render capability. registerRenderVideoTool registers a "render_video" tool schema with authentication via authInfo and delegates to existing triggerRenderVideo.

Sequence Diagram

sequenceDiagram
    participant Client
    participant APIRoute as API Route<br>/api/video/render
    participant Handler as renderVideoHandler
    participant Validator as validateRenderVideoBody
    participant Trigger as Trigger.dev
    participant Response as Response

    Client->>APIRoute: POST /api/video/render<br/>(with auth + body)
    APIRoute->>Handler: POST(request)
    Handler->>Handler: validateAuthContext<br/>(x-api-key or Bearer)
    Handler->>Validator: validateRenderVideoBody(body)
    Validator->>Validator: safeParseJson &<br/>Zod validation
    Validator->>Handler: RenderVideoBody | Error
    Handler->>Trigger: triggerRenderVideo(payload)<br/>with accountId
    Trigger->>Trigger: Dispatch "render-video" task
    Trigger->>Handler: {runId, ...metadata}
    Handler->>Response: {status: "processing",<br/>runId} + CORS headers
    Response->>Client: 200 OK
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🎬 A render route blooms in the API,
With Zod and auth standing guard with care,
Trigger.dev awaits the video's call,
MCP tools join the orchestral dance,
Frames compose into processing dreams. ✨

🚥 Pre-merge checks | ✅ 1 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Solid & Clean Code ⚠️ Warning Auth pattern inconsistency and DRY violation detected: registerUpdatePulseTool uses resolveAccountId() with async handling, while registerRenderVideoTool manually extracts accountId without validation; defaults duplicated in Zod schema and MCP handler. Implement consistent auth pattern using resolveAccountId() in registerRenderVideoTool and consolidate defaults to single source of truth.
✅ Passed checks (1 passed)
Check name Status Explanation
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into test

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/video-render-endpoint

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@lib/mcp/tools/render/registerRenderVideoTool.ts`:
- Around line 70-76: The code manually extracts accountId from extra.authInfo;
replace that with the shared resolver: import resolveAccountId and call await
resolveAccountId({ authInfo }) using the existing authInfo variable, then check
the returned { accountId, error } and return getToolResultError(error ||
"Authentication required.") if error or no accountId; update the handler where
authInfo is used (the async handler in registerRenderVideoTool) to follow the
same pattern as registerUpdatePulseTool and registerCreateNewArtistTool.
🧹 Nitpick comments (4)
lib/trigger/triggerRenderVideo.ts (1)

1-27: Clean implementation — good SRP and DRY foundation.

This function correctly serves as the single shared trigger point for both the REST endpoint and MCP tool. The type definition, JSDoc, and delegation are all solid.

One minor note: the "render-video" task ID on Line 26 is a magic string. Per coding guidelines, shared constants should live in lib/const.ts. If this identifier is referenced elsewhere (e.g., in the tasks repo), extracting it would reduce drift risk.

lib/render/validateRenderVideoBody.ts (1)

11-21: Schema defaults are duplicated in the MCP tool — consider reusing.

The defaults defined here (720×1280, 30 fps, 240 frames, "h264") are manually replicated in lib/mcp/tools/render/registerRenderVideoTool.ts (Lines 82–86) via ?? fallback expressions. If these values ever change, they'll need updating in two places.

Consider exporting the defaults (or the schema itself) so the MCP tool can reference the same source of truth, or at minimum add a comment linking the two.

lib/render/renderVideoHandler.ts (1)

45-48: Consider HTTP 202 Accepted for async task dispatch.

The endpoint returns 200 with status: "processing", but semantically 202 Accepted is the standard HTTP status for "request accepted, processing not yet complete." If this deviates from an established project convention, feel free to keep 200 — but 202 more clearly signals the async nature to API consumers.

lib/mcp/tools/render/registerRenderVideoTool.ts (1)

11-46: Duplicated schema constraints with validateRenderVideoBody.ts.

The validation constraints (e.g., .int().min(1).max(3840) for width/height, .min(1).max(60) for fps, .min(1).max(1800) for durationInFrames, the codec enum) are defined in both this file and lib/render/validateRenderVideoBody.ts. If constraints diverge over time, the MCP tool and REST endpoint would accept different inputs.

Consider extracting the shared constraints or base schema into a common module that both consumers can reference, while each applies its own .optional() vs .default() semantics on top.

Comment on lines +70 to +76
async (args, extra: RequestHandlerExtra<ServerRequest, ServerNotification>) => {
const authInfo = extra.authInfo as McpAuthInfo | undefined;
const accountId = authInfo?.extra?.accountId;

if (!accountId) {
return getToolResultError("Authentication required.");
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Search for resolveAccountId definition and usage
rg -n "resolveAccountId" --type=ts -B2 -A5

Repository: recoupable/api

Length of output: 3520


🏁 Script executed:

cat -n lib/mcp/tools/render/registerRenderVideoTool.ts | head -100

Repository: recoupable/api

Length of output: 4366


Import and use resolveAccountId() to derive the account ID from authentication.

The codebase establishes a consistent pattern for MCP tools: use resolveAccountId() with authInfo from extra.authInfo rather than manual extraction. This centralizes auth resolution logic and aligns with established patterns in registerUpdatePulseTool.ts and registerCreateNewArtistTool.ts.

Replace lines 71–76 with:

const authInfo = extra.authInfo as McpAuthInfo | undefined;
const { accountId, error } = await resolveAccountId({ authInfo });

if (error || !accountId) {
  return getToolResultError(error || "Authentication required.");
}

Also add the import at the top:

import { resolveAccountId } from "@/lib/mcp/resolveAccountId";
🤖 Prompt for AI Agents
In `@lib/mcp/tools/render/registerRenderVideoTool.ts` around lines 70 - 76, The
code manually extracts accountId from extra.authInfo; replace that with the
shared resolver: import resolveAccountId and call await resolveAccountId({
authInfo }) using the existing authInfo variable, then check the returned {
accountId, error } and return getToolResultError(error || "Authentication
required.") if error or no accountId; update the handler where authInfo is used
(the async handler in registerRenderVideoTool) to follow the same pattern as
registerUpdatePulseTool and registerCreateNewArtistTool.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant