GhostfeedGhostfeed MCPv5.3.0
MCP docs

UGC Reactions

Create UGC reaction videos over the two-phase flow: import a template (link, local upload, or an inspiration clip), render an avatar into the source pose (the first frame), then, after the user approves the frame, animate it — cloning the reference motion or from a text prompt. Bulk creation only; cropping, editing, and deletion stay in the dashboard.

list_reaction_templates

READ

REST: GET /api/v2/reaction-templates

FIRST source search for every new reaction request unless the user explicitly supplied an exact source. List reaction TEMPLATES usable in this workspace: platform stock plus your own imported/uploaded motion clips. Each has an id, name, category, duration, thumbnail, and status. Captioned clips also carry opensOn (the opening still your avatar gets rendered into) and motion (what happens in the clip); older clips may not have them yet. Choose primarily on opensOn. A prompt-mode video may reuse the opening pose without copying the source motion. Use motion only to judge a requested clone, and never paste it into generate_reaction_video as the prompt. If no template has a reasonable opening-pose match, search list_inspiration_reactions next.

Input schema
{
  "type": "object",
  "properties": {
    "workspace": {
      "description": "Target workspace: a slug, id, or exact name from list_workspaces. Omit to use this connection's default workspace. Must be inside the credential's grant and your current access; overrides the connection-level X-Workspace-Id header.",
      "type": "string"
    },
    "mine": {
      "type": "boolean"
    },
    "includeUnusable": {
      "description": "Include templates that cannot be generated from yet: ones still importing, ones that failed, and ones over 30s awaiting a crop. Off by default, because generating from them is refused anyway.",
      "type": "boolean"
    },
    "page": {
      "description": "Page number (default 1).",
      "type": "integer",
      "minimum": 1,
      "maximum": 9007199254740991
    },
    "limit": {
      "description": "Per page (max 25; lower than other tools because these rows carry long descriptions). Each tool documents its own default.",
      "type": "integer",
      "minimum": 1,
      "maximum": 25
    }
  },
  "$schema": "http://json-schema.org/draft-07/schema#"
}
Example response
{
  "success": true,
  "data": {
    "workspace": {
      "id": "6a18c36ee586c7e44e4ece67",
      "name": "Client A",
      "slug": "client-a"
    },
    "templates": [
      {
        "id": "user-1a2b3c4d-1737100000000",
        "name": "Excited point",
        "category": "Reactions",
        "durationSeconds": 4.2,
        "thumbnailUrl": "https://cdn.ghostfeed.ai/reactions/user-1a2b3c4d/thumbnail.jpg",
        "kind": "owned",
        "status": "completed",
        "mention": "@template:Excited point (user-1a2b3c4d-1737100000000)",
        "opensOn": "Medium shot of a woman at a desk, looking just off camera, hands raised mid-gesture.",
        "motion": "She points at the camera, leans in, and breaks into a grin."
      }
    ],
    "total": 1,
    "page": 1,
    "pages": 1,
    "nextSteps": "Pick a template, then generate_reaction_frames with its id and one or more avatars."
  }
}

list_reaction_frames

READ

REST: GET /api/v2/reaction-frames

List the reaction FRAMES on the workspace board, newest first, including ones a person made in the dashboard rather than through an agent. Each has a frameId, image, the avatar it used, its source template, the videoStage of the shell it lives in, and isAnimated (false means nothing has been animated from it yet, so it is free to use). Use this to act on existing work: "animate the frame I made this morning" starts here, then pass the frameId to generate_reaction_video.

Input schema
{
  "type": "object",
  "properties": {
    "workspace": {
      "description": "Target workspace: a slug, id, or exact name from list_workspaces. Omit to use this connection's default workspace. Must be inside the credential's grant and your current access; overrides the connection-level X-Workspace-Id header.",
      "type": "string"
    },
    "avatar": {
      "type": "string",
      "minLength": 1
    },
    "templateId": {
      "type": "string",
      "minLength": 1
    },
    "page": {
      "description": "Page number (default 1).",
      "type": "integer",
      "minimum": 1,
      "maximum": 9007199254740991
    },
    "limit": {
      "description": "Per page (max 100). Each tool documents its own default.",
      "type": "integer",
      "minimum": 1,
      "maximum": 100
    }
  },
  "$schema": "http://json-schema.org/draft-07/schema#"
}
Example response
{
  "success": true,
  "data": {
    "workspace": {
      "id": "6a18c36ee586c7e44e4ece67",
      "name": "Client A",
      "slug": "client-a"
    },
    "frames": [
      {
        "frameId": "4b706a83-c035-427d-9f68-1091101c6dec",
        "imageUrl": "https://cdn.ghostfeed.ai/frames/frame-1737100000000-1.png",
        "avatarId": "d7d06ea3-a574-4fe0-9a39-d189a0b68698",
        "avatarName": "Ashley Gram",
        "templateId": "user-1a2b3c4d-1737100000000",
        "videoId": "1cc27551-5ad9-42bd-9a9c-899f83dcfec6",
        "videoStage": "selecting-frame",
        "isAnimated": false,
        "createdAt": "2026-07-20T02:22:50.374Z",
        "mention": "@frame:Ashley Gram (4b706a83-c035-427d-9f68-1091101c6dec)",
        "dashboardUrl": "https://app.ghostfeed.ai/w/client-a/ugc-reactions?frame=4b706a83-c035-427d-9f68-1091101c6dec"
      }
    ],
    "total": 1,
    "page": 1,
    "pages": 1,
    "nextSteps": "Pass a frameId to generate_reaction_video to animate it."
  }
}

list_reaction_videos

READ

REST: GET /api/v2/reaction-videos

List the reaction VIDEOS in this workspace, newest first, agent-made or not. Each has a videoId, name, stage, the avatar, and a playable videoUrl once stage is "complete". Use it to report what is finished or still rendering. Managing and deleting videos stays in the dashboard.

Input schema
{
  "type": "object",
  "properties": {
    "workspace": {
      "description": "Target workspace: a slug, id, or exact name from list_workspaces. Omit to use this connection's default workspace. Must be inside the credential's grant and your current access; overrides the connection-level X-Workspace-Id header.",
      "type": "string"
    },
    "avatar": {
      "type": "string",
      "minLength": 1
    },
    "stage": {
      "type": "string",
      "minLength": 1
    },
    "page": {
      "description": "Page number (default 1).",
      "type": "integer",
      "minimum": 1,
      "maximum": 9007199254740991
    },
    "limit": {
      "description": "Per page (max 100). Each tool documents its own default.",
      "type": "integer",
      "minimum": 1,
      "maximum": 100
    }
  },
  "$schema": "http://json-schema.org/draft-07/schema#"
}
Example response
{
  "success": true,
  "data": {
    "workspace": {
      "id": "6a18c36ee586c7e44e4ece67",
      "name": "Client A",
      "slug": "client-a"
    },
    "videos": [
      {
        "videoId": "b9b2a07c-2119-455f-aebf-e7d0b2f32182",
        "name": "Excited point - Ashley Gram",
        "stage": "complete",
        "videoUrl": "https://cdn.ghostfeed.ai/generated-videos/b9b2a07c/video.mp4",
        "avatarId": "d7d06ea3-a574-4fe0-9a39-d189a0b68698",
        "avatarName": "Ashley Gram",
        "createdAt": "2026-07-20T02:09:51.871Z",
        "dashboardUrl": "https://app.ghostfeed.ai/w/client-a/ugc-reactions?video=b9b2a07c-2119-455f-aebf-e7d0b2f32182"
      }
    ],
    "total": 1,
    "page": 1,
    "pages": 1,
    "nextSteps": "stage \"complete\" means the clip is finished and videoUrl plays it."
  }
}

list_inspiration_reactions

READ

REST: GET /api/v2/inspiration-reactions

SECOND source search for every new reaction request, after list_reaction_templates has no suitable opening pose. Browse the curated inspiration library of reaction clips (ids, names, categories, thumbnails). Captioned clips also carry opensOn (the opening still your avatar gets rendered into) and motion (what happens in the clip). Choose primarily on opensOn. A prompt-mode video may reuse the opening pose without copying the source motion. Use motion only to judge a requested clone, and never paste it into generate_reaction_video as the prompt. Pass an inspiration id to import_reaction_template or generate_reaction_frames and it is auto-saved to this workspace as a template first. Only fall back to a custom referenceImageUrl after both libraries lack a reasonable match.

Input schema
{
  "type": "object",
  "properties": {
    "workspace": {
      "description": "Target workspace: a slug, id, or exact name from list_workspaces. Omit to use this connection's default workspace. Must be inside the credential's grant and your current access; overrides the connection-level X-Workspace-Id header.",
      "type": "string"
    },
    "category": {
      "type": "string",
      "maxLength": 80
    },
    "page": {
      "description": "Page number (default 1).",
      "type": "integer",
      "minimum": 1,
      "maximum": 9007199254740991
    },
    "limit": {
      "description": "Per page (max 100). Each tool documents its own default.",
      "type": "integer",
      "minimum": 1,
      "maximum": 100
    }
  },
  "$schema": "http://json-schema.org/draft-07/schema#"
}
Example response
{
  "success": true,
  "data": {
    "workspace": {
      "id": "6a18c36ee586c7e44e4ece67",
      "name": "Client A",
      "slug": "client-a"
    },
    "inspiration": [
      {
        "id": "insp_9f8e7d6c",
        "name": "Grandma tastes pickle",
        "category": "Food",
        "durationSeconds": 6.1,
        "thumbnailUrl": "https://cdn.ghostfeed.ai/inspiration/insp_9f8e7d6c/thumb.jpg",
        "mention": "@inspiration:Grandma tastes pickle (insp_9f8e7d6c)"
      }
    ],
    "total": 1,
    "page": 1,
    "pages": 1,
    "nextSteps": "Pass an inspiration id to generate_reaction_frames; it is auto-saved as a template."
  }
}

list_reaction_video_modes

READ

REST: GET /api/v2/reaction-video-modes

List the video modes for the reaction video phase, with per-second credit cost and whether each needs a prompt or supports audio. Two families: CLONE modes reproduce a template motion (default one_to_one_standard, Kling motion-control, no prompt); PROMPT modes animate the frame from text (default seedance_2_0_fast). Use a clone mode only when the user wants to reproduce the reference video motion.

No input. Call with no arguments.

Example response
{
  "success": true,
  "data": {
    "modes": [
      {
        "mode": "one_to_one_standard",
        "label": "1:1 Standard",
        "family": "clone",
        "creditsPerSecond": 1,
        "supportsAudio": false,
        "needsPrompt": false,
        "isDefault": true
      },
      {
        "mode": "seedance_2_0_fast",
        "label": "Seedance 2.0 Fast",
        "family": "prompt",
        "creditsPerSecond": 1.5,
        "supportsAudio": false,
        "needsPrompt": true,
        "durationOptions": [
          4,
          5,
          6,
          7,
          8,
          9,
          10,
          11,
          12,
          13,
          14,
          15
        ],
        "supportsAutoDuration": true,
        "isDefault": true
      }
    ],
    "defaults": {
      "clone": "one_to_one_standard",
      "prompt": "seedance_2_0_fast"
    },
    "nextSteps": "Clone modes reuse a template motion; prompt modes animate from text."
  }
}

request_reaction_upload

WRITEIDEMPOTENT

REST: POST /api/v2/reactions/upload-url

Get a short-lived presigned R2 upload URL for a LOCAL video file (a video is too large for a tool argument). PUT the raw bytes to uploadUrl with the given Content-Type, then pass the returned fileUrl to import_reaction_template as uploadedFileUrl. Costs no credits.

Input schema
{
  "type": "object",
  "properties": {
    "workspace": {
      "type": "string",
      "minLength": 1,
      "description": "Required write target: a workspace slug or id from list_workspaces. Writes never inherit the connection default."
    },
    "filename": {
      "type": "string",
      "minLength": 1,
      "maxLength": 200
    },
    "contentType": {
      "type": "string",
      "minLength": 3,
      "maxLength": 100
    }
  },
  "required": [
    "workspace"
  ],
  "$schema": "http://json-schema.org/draft-07/schema#"
}
Example response
{
  "success": true,
  "data": {
    "workspace": {
      "id": "6a18c36ee586c7e44e4ece67",
      "name": "Client A",
      "slug": "client-a"
    },
    "upload": {
      "method": "PUT",
      "uploadUrl": "https://abc123.r2.cloudflarestorage.com/shortsbro/users/1a2b3c4d/reactions-uploads/2026/07/18/uuid.mp4?X-Amz-Signature=...",
      "fileUrl": "https://cdn.ghostfeed.ai/users/1a2b3c4d/reactions-uploads/2026/07/18/uuid.mp4",
      "contentType": "video/mp4",
      "expiresInSeconds": 900
    },
    "nextSteps": "PUT the bytes to uploadUrl, then import_reaction_template with uploadedFileUrl=fileUrl."
  }
}

import_reaction_template

WRITE

REST: POST /api/v2/reaction-templates/import

Import a reaction TEMPLATE from exactly one source: sourceUrl (a TikTok/Instagram link), uploadedFileUrl (from request_reaction_upload, the local-file path), or inspirationId (a curated clip). Async: returns a reaction_import generation to poll with get_generation. succeeded = ready to use; needs_action = the source is 30-120s and must be cropped in the dashboard first (open dashboardUrl); >120s is rejected. Costs no credits. Managing, cropping, and deleting templates all happen in the dashboard.

Input schema
{
  "type": "object",
  "properties": {
    "workspace": {
      "type": "string",
      "minLength": 1,
      "description": "Required write target: a workspace slug or id from list_workspaces. Writes never inherit the connection default."
    },
    "name": {
      "type": "string",
      "minLength": 1,
      "maxLength": 120
    },
    "category": {
      "type": "string",
      "minLength": 1,
      "maxLength": 80
    },
    "sourceUrl": {
      "type": "string",
      "format": "uri"
    },
    "uploadedFileUrl": {
      "type": "string",
      "format": "uri"
    },
    "inspirationId": {
      "type": "string",
      "minLength": 1
    },
    "idempotencyKey": {
      "description": "A stable key to reuse if you retry this exact request; a repeat with the same key is NOT charged again.",
      "type": "string",
      "maxLength": 200
    }
  },
  "required": [
    "workspace",
    "name"
  ],
  "$schema": "http://json-schema.org/draft-07/schema#"
}
Example response
{
  "success": true,
  "data": {
    "workspace": {
      "id": "6a18c36ee586c7e44e4ece67",
      "name": "Client A",
      "slug": "client-a"
    },
    "generation": {
      "id": "gen_5b6c7d8e9f0a",
      "kind": "reaction_import",
      "state": "running",
      "title": "Excited point",
      "input": {
        "templateId": "user-1a2b3c4d-1737100000000",
        "source": "link"
      },
      "outputs": [],
      "dashboardUrl": "https://ghostfeed.ai/w/client-a/ugc-reactions?template=user-1a2b3c4d-1737100000000&ref=agent",
      "createdAt": "2026-07-18T12:00:00.000Z",
      "updatedAt": "2026-07-18T12:00:00.000Z"
    },
    "templateId": "user-1a2b3c4d-1737100000000",
    "mention": "@template:Excited point (user-1a2b3c4d-1737100000000)",
    "nextSteps": "Poll get_generation until terminal. needs_action = crop in the dashboard first."
  }
}

generate_reaction_frames

WRITE

REST: POST /api/v2/reactions/frames

PHASE 1 of a UGC reaction: render one or more avatars into the source pose, ONE frame per avatar. LIBRARY-FIRST RULE: unless the user explicitly supplied an exact source, call list_reaction_templates first, then list_inspiration_reactions if needed, and use a reasonable library opening-pose match. A custom motion request can still use a library first frame with prompt-mode animation. Use referenceImageUrl only after both Ghostfeed libraries lack a suitable opening pose, and tell the user why the custom fallback was necessary. Source is exactly one of templateId, inspirationId (auto-saved to a template first), or referenceImageUrl. Async: returns one reaction_frame generation per avatar to poll; the finished frame is output.url and output.id is the frameId. Charges the image-model cost per avatar as frames render (gemini_flash unless imageModel is set). STOP after this: show the frame(s) to the user and get approval BEFORE generate_reaction_video, because video credits are far higher.

Input schema
{
  "type": "object",
  "properties": {
    "workspace": {
      "type": "string",
      "minLength": 1,
      "description": "Required write target: a workspace slug or id from list_workspaces. Writes never inherit the connection default."
    },
    "avatars": {
      "minItems": 1,
      "maxItems": 10,
      "type": "array",
      "items": {
        "type": "string",
        "minLength": 1
      }
    },
    "templateId": {
      "type": "string",
      "minLength": 1
    },
    "inspirationId": {
      "type": "string",
      "minLength": 1
    },
    "referenceImageUrl": {
      "type": "string",
      "format": "uri"
    },
    "imageModel": {
      "type": "string",
      "enum": [
        "gemini_flash_lite",
        "gemini_flash",
        "gemini_pro",
        "gpt_low",
        "gpt_medium",
        "gpt_high"
      ]
    },
    "stylingPrompt": {
      "type": "string",
      "maxLength": 500
    },
    "aspectRatio": {
      "type": "string",
      "enum": [
        "9:16",
        "16:9",
        "1:1"
      ]
    },
    "idempotencyKey": {
      "description": "A stable key to reuse if you retry this exact request; a repeat with the same key is NOT charged again.",
      "type": "string",
      "maxLength": 200
    }
  },
  "required": [
    "workspace",
    "avatars"
  ],
  "$schema": "http://json-schema.org/draft-07/schema#"
}
Example response
{
  "success": true,
  "data": {
    "workspace": {
      "id": "6a18c36ee586c7e44e4ece67",
      "name": "Client A",
      "slug": "client-a"
    },
    "generations": [
      {
        "id": "gen_frame_aa11bb22",
        "kind": "reaction_frame",
        "state": "running",
        "phase": "generating-frames",
        "input": {
          "videoId": "vid_aa11bb22",
          "source": "template",
          "imageModel": "gemini_flash"
        },
        "outputs": [],
        "dashboardUrl": "https://ghostfeed.ai/w/client-a/ugc-reactions?ref=agent",
        "createdAt": "2026-07-18T12:01:00.000Z",
        "updatedAt": "2026-07-18T12:01:00.000Z"
      }
    ],
    "estimatedCreditCost": 1,
    "creditsRemaining": 46,
    "nextSteps": "Poll each generation; the frame image is output.url. Get the user to approve before generate_reaction_video."
  }
}

generate_reaction_video

WRITE

REST: POST /api/v2/reactions/videos

PHASE 2 of a UGC reaction: animate approved frame(s) into video. Only call after the user has reviewed and approved the frames. mode picks the family: a CLONE mode (one_to_one_*) copies the motion straight from the source clip and takes no prompt; a PROMPT mode (default seedance_2_0_fast) animates from text and needs a prompt. In PROMPT mode the source template supplied only the first frame — the movement comes from what the USER asked for, so write prompt from their words. Never paste the template motion text in as the prompt: that field exists to help you CHOOSE a clip, and using it would animate the reference performance instead of the one the user described. Duration is auto unless durationSeconds is set (ignored by clone modes, which follow the source clip). Async: returns one reaction_video generation per frame to poll; the clip is output.url. Charges per second of video and reports creditsSpent/creditsRemaining.

Input schema
{
  "type": "object",
  "properties": {
    "workspace": {
      "type": "string",
      "minLength": 1,
      "description": "Required write target: a workspace slug or id from list_workspaces. Writes never inherit the connection default."
    },
    "frameIds": {
      "minItems": 1,
      "maxItems": 8,
      "type": "array",
      "items": {
        "type": "string",
        "minLength": 1
      }
    },
    "mode": {
      "type": "string",
      "enum": [
        "one_to_one_standard",
        "one_to_one_clone_premium",
        "grok",
        "pixverse",
        "pixverse_1080p",
        "kling_2_5_turbo",
        "seedance_2_0",
        "seedance_2_0_fast",
        "seedance_2_0_mini"
      ]
    },
    "prompt": {
      "type": "string",
      "maxLength": 2000
    },
    "durationSeconds": {
      "type": "number",
      "exclusiveMinimum": 0
    },
    "enableAudio": {
      "type": "boolean"
    },
    "maxCredits": {
      "description": "Refuse to start any clip that would cost more than this many credits. Clone modes take their length from the source clip, so a 30s template is 30 credits at 1 credit/second and 60 at 2. A ceiling of MAX_REACTION_VIDEO_CREDITS credits per clip always applies; this only tightens it.",
      "type": "integer",
      "exclusiveMinimum": 0,
      "maximum": 9007199254740991
    },
    "idempotencyKey": {
      "description": "A stable key to reuse if you retry this exact request; a repeat with the same key is NOT charged again.",
      "type": "string",
      "maxLength": 200
    }
  },
  "required": [
    "workspace",
    "frameIds"
  ],
  "$schema": "http://json-schema.org/draft-07/schema#"
}
Example response
{
  "success": true,
  "data": {
    "workspace": {
      "id": "6a18c36ee586c7e44e4ece67",
      "name": "Client A",
      "slug": "client-a"
    },
    "generations": [
      {
        "id": "gen_vid_cc33dd44",
        "kind": "reaction_video",
        "state": "running",
        "phase": "generating-video",
        "input": {
          "videoId": "vid_aa11bb22",
          "frameId": "frm_ee55ff66",
          "mode": "seedance_2_0_fast"
        },
        "outputs": [],
        "dashboardUrl": "https://ghostfeed.ai/w/client-a/ugc-reactions?video=vid_aa11bb22&ref=agent",
        "createdAt": "2026-07-18T12:05:00.000Z",
        "updatedAt": "2026-07-18T12:05:00.000Z"
      }
    ],
    "skipped": [],
    "creditsSpent": 8,
    "creditsRemaining": 38,
    "nextSteps": "Poll each generation until its state is succeeded; the clip is output.url. Report spend to the user."
  }
}

regenerate_reaction_frame

WRITE

REST: POST /api/v2/reactions/frames/regenerate

Redo a frame the user was not happy with. Identify it with EITHER frameId (any frame from list_reaction_frames, including ones a person made in the dashboard) OR generationId (a frame this API started); pass exactly one. Re-runs frame generation on the same avatar + source and charges another frame credit. Poll the same generation until it succeeds with a fresh frame, then get approval before generate_reaction_video.

Input schema
{
  "type": "object",
  "properties": {
    "workspace": {
      "type": "string",
      "minLength": 1,
      "description": "Required write target: a workspace slug or id from list_workspaces. Writes never inherit the connection default."
    },
    "generationId": {
      "description": "The generationId of a frame you started through the agent API.",
      "type": "string",
      "minLength": 1
    },
    "frameId": {
      "description": "A frameId from list_reaction_frames. Use this for any frame, including ones a person made in the dashboard, which have no generationId. Pass exactly one of the two.",
      "type": "string",
      "minLength": 1
    }
  },
  "required": [
    "workspace"
  ],
  "$schema": "http://json-schema.org/draft-07/schema#"
}
Example response
{
  "success": true,
  "data": {
    "workspace": {
      "id": "6a18c36ee586c7e44e4ece67",
      "name": "Client A",
      "slug": "client-a"
    },
    "generation": {
      "id": "gen_frame_aa11bb22",
      "kind": "reaction_frame",
      "state": "running",
      "phase": "generating-frames",
      "input": {
        "videoId": "vid_aa11bb22",
        "source": "template",
        "imageModel": "gemini_flash"
      },
      "outputs": [],
      "dashboardUrl": "https://ghostfeed.ai/w/client-a/ugc-reactions?ref=agent",
      "createdAt": "2026-07-18T12:01:00.000Z",
      "updatedAt": "2026-07-18T12:07:00.000Z"
    },
    "nextSteps": "Poll until it succeeds with a fresh frame, then get approval before the video."
  }
}