# Viraloop API, full reference for agents > Concatenated API reference and guides. Regenerated by npm run generate:agents. # Viraloop API reference > Generated from src/backend/api/v1/spec/registry.mjs by npm run generate:agents. Do not edit by hand. Base URL: `https://viraloop.io/api/v1` Authentication: `Authorization: Bearer vl_live_...` (or `X-API-Key`). Create keys at https://viraloop.io/settings/developers. Envelope: success responses are `{ "success": true, "data": ..., "pagination"?: { total, page, limit, pages } }`. Errors are `{ "success": false, "error": { "type", "message" } }` with types: `invalid_input`, `unauthorized`, `forbidden_scope`, `not_found`, `conflict`, `insufficient_credits`, `usage_limit_reached`, `rate_limited`, `internal_error`. ## me ### Introspect the API key `GET /me` Returns the team behind the credentials: name, credit balance, plan, default workspace and the scopes granted to the key. Call this first to verify auth works. - Scopes: any valid key - Credits: none - CLI: `viraloop whoami` - MCP tool: `viraloop_get_me` Example: ```bash curl -s "https://viraloop.io/api/v1/me" \ -H "Authorization: Bearer $VIRALOOP_API_KEY" ``` Response: ```json { "success": true, "data": { "teamId": "665f1b2a9c31a2b3c4d5e6f7", "teamName": "Acme", "credits": 420, "plan": "Growth (monthly)", "planValidUntil": "2026-08-01T00:00:00.000Z", "workspaceId": "665f1b2a9c31a2b3c4d5e6f8", "auth": { "via": "api_key", "keyPrefix": "vl_live_a1b2c3d4", "scopes": [ "posts:read", "posts:write" ], "legacy": false } } } ``` ## credits ### Credit balance and ledger `GET /credits` Returns the team's current credit balance plus a paginated ledger of credit movements (top-ups, spending, rewards, refunds). Turbo content generation does not consume credits; influencer media generation does (createInfluencerVideo: 5 credits per second of video, createInfluencer preview: 10). Failed generations are refunded automatically; running out of credits returns HTTP 402 with error type insufficient_credits. - Scopes: `credits:read` - Credits: none - CLI: `viraloop credits` - MCP tool: `viraloop_get_credits` Query parameters: | Name | Type | Required | Description | | --- | --- | --- | --- | | `page` | integer | no | Page number, starting at 1 | | `limit` | integer | no | Items per page (max 100) | | `type` | `recurring` \| `topup` \| `spending` \| `trial` \| `spin` \| `reward` \| `refund` | no | Filter by entry type | Example: ```bash curl -s "https://viraloop.io/api/v1/credits" \ -H "Authorization: Bearer $VIRALOOP_API_KEY" ``` Response: ```json { "success": true, "data": { "balance": 420, "entries": [ { "id": "665f1b2a9c31a2b3c4d5e6f9", "credits": -10, "type": "spending", "spendingType": "video_generation", "model": "veo3_fast", "createdAt": "2026-07-01T12:00:00.000Z" } ] }, "pagination": { "total": 1, "page": 1, "limit": 20, "pages": 1 } } ``` ## workspaces ### List workspaces `GET /workspaces` Lists the team's workspaces (brands). Most endpoints accept an optional workspaceId and default to the team's default workspace. - Scopes: `workspaces:read` - Credits: none - CLI: `viraloop workspaces list` - MCP tool: `viraloop_list_workspaces` Example: ```bash curl -s "https://viraloop.io/api/v1/workspaces" \ -H "Authorization: Bearer $VIRALOOP_API_KEY" ``` Response: ```json { "success": true, "data": [ { "id": "665f1b2a9c31a2b3c4d5e6f8", "name": "Acme", "isDefault": true, "website": { "url": "https://acme.com", "domain": "acme.com" }, "contentAngles": [ { "title": "Client Revision Hell", "description": "..." } ], "preferences": { "timezone": "America/New_York", "contentLanguage": "English" } } ] } ``` ### Create a workspace `POST /workspaces` Creates a workspace (brand) under the team. Provide the brand's website and the brand profile (identity, positioning, content angles) is generated in the background: poll getWorkspace until brandContext.status is ready. One domain can be claimed by only one workspace across all of Viraloop. - Scopes: `workspaces:write` - Credits: none - Rate limit: 60 per 3600s - Supports `Idempotency-Key` header - CLI: `viraloop workspaces create` - MCP tool: `viraloop_create_workspace` Body fields: | Name | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | yes | | | `websiteUrl` | string | no | The brand's website; seeds the auto-generated brand profile | | `logoUrl` | string | no | | Example: ```bash curl -s -X POST "https://viraloop.io/api/v1/workspaces" \ -H "Authorization: Bearer $VIRALOOP_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name":"Acme","websiteUrl":"https://acme.com"}' ``` Response: ```json { "success": true, "data": { "id": "665f1b2a9c31a2b3c4d5e6f9", "name": "Acme", "isDefault": false, "website": { "url": "https://acme.com", "domain": "acme.com" }, "preferences": { "timezone": "", "contentLanguage": "English" } } } ``` ### Get a workspace `GET /workspaces/{id}` Returns one workspace with its brand identity, positioning, tone of voice, content angles and preferences. - Scopes: `workspaces:read` - Credits: none - CLI: `viraloop workspaces get ` Example: ```bash curl -s "https://viraloop.io/api/v1/workspaces/" \ -H "Authorization: Bearer $VIRALOOP_API_KEY" ``` Response: ```json { "success": true, "data": { "id": "665f1b2a9c31a2b3c4d5e6f8", "name": "Acme", "isDefault": true, "identity": { "coreIdentity": "...", "productOffering": "..." }, "positioning": { "mission": "..." }, "toneVoice": { "dos": [ "..." ], "donts": [ "..." ] }, "contentAngles": [ { "title": "...", "description": "..." } ], "preferences": { "timezone": "UTC", "contentLanguage": "English" } } } ``` ## accounts ### List connected social accounts `GET /accounts` Lists the TikTok, Instagram and YouTube accounts connected to the workspace. Use the returned ids in selectedAccounts when creating posts. Connecting accounts (OAuth) happens in the web app, not through this API. - Scopes: `accounts:read` - Credits: none - CLI: `viraloop accounts list` - MCP tool: `viraloop_list_accounts` Query parameters: | Name | Type | Required | Description | | --- | --- | --- | --- | | `platform` | `tiktok` \| `instagram` \| `youtube` | no | | | `ownerType` | `brand` \| `influencer` | no | | | `workspaceId` | string | no | Workspace to operate in. Defaults to the team's default workspace. | Example: ```bash curl -s "https://viraloop.io/api/v1/accounts" \ -H "Authorization: Bearer $VIRALOOP_API_KEY" ``` Response: ```json { "success": true, "data": [ { "id": "665f1b2a9c31a2b3c4d5e700", "platform": "tiktok", "ownerType": "brand", "username": "acme.hq", "displayName": "Acme", "label": "Main", "isDefault": true } ] } ``` ## generations ### Generate AI post suggestions `POST /generations` Generates a batch of ready-to-post content suggestions (video decks with captions, hashtags and rationale) using the workspace's brand context and Turbo configuration. Pass prompt to set the subject, and format to pick wall of text, slideshow or green screen meme. Synchronous: the request returns when generation finishes, typically 5 to 60 seconds depending on count. Review the results, then publish the ones you like via POST /posts with suggestionId. - Scopes: `generations:write` - Credits: none - Rate limit: 10 per 300s - Supports `Idempotency-Key` header - CLI: `viraloop generate` - MCP tool: `viraloop_generate_content` Body fields: | Name | Type | Required | Description | | --- | --- | --- | --- | | `count` | integer | no | How many suggestions to generate (1-10) | | `format` | `walloftext` \| `slideshow` \| `greenscreen` | no | Force one content format. Omit to use the workspace's configured mix. | | `prompt` | string | no | What the batch should be about, e.g. 'why founders burn out on content'. Omit to let Turbo pick from the workspace's content angles. | | `influencerId` | string | no | Feature this AI influencer in every suggestion | | `workspaceId` | string | no | Workspace to operate in. Defaults to the team's default workspace. | Example: ```bash curl -s -X POST "https://viraloop.io/api/v1/generations" \ -H "Authorization: Bearer $VIRALOOP_API_KEY" \ -H "Content-Type: application/json" \ -d '{"count":3,"format":"walloftext"}' ``` Response: ```json { "success": true, "data": [ { "id": "665f1b2a9c31a2b3c4d5e701", "format": "walloftext", "caption": "POV: the client asks for one small change", "postCaption": "Every designer knows this feeling", "hashtags": [ "design", "clientwork" ], "status": "pending", "statusUrl": "/api/v1/generations/665f1b2a9c31a2b3c4d5e701" } ] } ``` ### List suggestions `GET /generations` Lists generated content suggestions in the workspace queue (automation-owned suggestions are excluded). Filter by status: pending (awaiting review), accepted, scheduled. - Scopes: `generations:read` - Credits: none - CLI: `viraloop generations list` - MCP tool: `viraloop_list_generations` Query parameters: | Name | Type | Required | Description | | --- | --- | --- | --- | | `status` | `pending` \| `accepted` \| `scheduled` \| `deleted` | no | | | `page` | integer | no | Page number, starting at 1 | | `limit` | integer | no | Items per page (max 100) | | `workspaceId` | string | no | Workspace to operate in. Defaults to the team's default workspace. | Example: ```bash curl -s "https://viraloop.io/api/v1/generations" \ -H "Authorization: Bearer $VIRALOOP_API_KEY" ``` Response: ```json { "success": true, "data": [ { "id": "665f1b2a9c31a2b3c4d5e701", "format": "walloftext", "caption": "POV: the client asks for one small change", "status": "pending", "createdAt": "2026-07-02T10:00:00.000Z" } ], "pagination": { "total": 12, "page": 1, "limit": 20, "pages": 1 } } ``` ### Get a suggestion `GET /generations/{id}` Returns one suggestion including its full deck (the editable render blueprint), influencer, angle, rationale and remix source. The deck IS the free preview: render its slide images / clips with the caption text boxes overlaid client-side to let a user accept or skip. Previewing and accepting are free; billing only happens when media is downloaded or published. - Scopes: `generations:read` - Credits: none - CLI: `viraloop generations get ` - MCP tool: `viraloop_get_generation` Example: ```bash curl -s "https://viraloop.io/api/v1/generations/" \ -H "Authorization: Bearer $VIRALOOP_API_KEY" ``` Response: ```json { "success": true, "data": { "id": "665f1b2a9c31a2b3c4d5e701", "format": "walloftext", "caption": "POV: the client asks for one small change", "postCaption": "Every designer knows this feeling", "hashtags": [ "design", "clientwork" ], "why": [ "Relatable pain point", "Strong hook" ], "angle": { "title": "Client Revision Hell" }, "status": "pending", "deck": { "ratio": "9:16", "textBoxes": [] } } } ``` ### Accept a generation into the library `POST /generations/{id}/accept` Accepts a generated suggestion and saves it to the content library, returning a contentId. That id is what GET /content/{id}/download (fetch the media files) and POST /posts (publish) operate on. Idempotent: re-accepting returns the same contentId. Counts toward the plan's monthly content allowance. - Scopes: `generations:write` - Credits: none - Supports `Idempotency-Key` header - CLI: `viraloop generations accept ` - MCP tool: `viraloop_accept_generation` Query parameters: | Name | Type | Required | Description | | --- | --- | --- | --- | | `workspaceId` | string | no | Workspace to operate in. Defaults to the team's default workspace. | Example: ```bash curl -s -X POST "https://viraloop.io/api/v1/generations//accept" \ -H "Authorization: Bearer $VIRALOOP_API_KEY" ``` Response: ```json { "success": true, "data": { "id": "665f1b2a9c31a2b3c4d5e711", "status": "accepted", "contentId": "665f1b2a9c31a2b3c4d5e757" } } ``` ## posts ### Create and schedule a post `POST /posts` Schedules a post to one or more connected accounts. Four content sources, checked in order: suggestionId (publish a generated suggestion), contentId (publish a saved studio video from any /content operation), deck + format (publish a raw deck), or videoUrl / images (publish your own media). Deck-based posts are rendered server-side after this call returns; the response is 202 with a statusUrl to poll. schedule asap posts as soon as the render is ready; schedule scheduled requires a future scheduledTime. Supports the Idempotency-Key header. - Scopes: `posts:write` - Credits: none - Rate limit: 60 per 300s - Supports `Idempotency-Key` header - Terminal states: `posted`, `partial`, `completed`, `failed` - CLI: `viraloop posts create` - MCP tool: `viraloop_create_post` Body fields: | Name | Type | Required | Description | | --- | --- | --- | --- | | `suggestionId` | string | no | Publish this generated suggestion (from /generations) | | `contentId` | string | no | Publish a saved studio video (from any /content operation). Finished videos post as-is; deck formats render first. | | `deck` | object | no | Raw deck object (advanced; usually use suggestionId or contentId) | | `format` | `walloftext` \| `slideshow` \| `greenscreen` \| `grid2x2` \| `listicle` \| `askmeanything` \| `ranking` \| `splitscreen` \| `singlefadein` \| `videohookdemo` \| `talkingheadgreenscreen` | no | Deck format, required when deck is provided | | `videoUrl` | string | no | Publicly reachable video URL to post as-is | | `thumbnailUrl` | string | no | | | `images` | array | no | Image URLs to post as an image/slideshow post | | `selectedAccounts` | object | yes | Social account ids per platform (from GET /accounts). At least one non-empty platform array is required. | | `schedule` | `asap` \| `scheduled` | no | | | `scheduledTime` | string | no | Required when schedule is scheduled; must be in the future | | `timezone` | string | no | IANA timezone, default UTC | | `caption` | string | no | Post caption/description. Defaults to the suggestion's. | | `hashtags` | array | no | | | `ownerType` | `brand` \| `influencer` | no | | | `influencerId` | string | no | Required when ownerType is influencer | | `tiktokSendToInbox` | boolean | no | Send to TikTok inbox as draft instead of publishing | | `workspaceId` | string | no | Workspace to operate in. Defaults to the team's default workspace. | Example: ```bash curl -s -X POST "https://viraloop.io/api/v1/posts" \ -H "Authorization: Bearer $VIRALOOP_API_KEY" \ -H "Content-Type: application/json" \ -d '{"suggestionId":"665f1b2a9c31a2b3c4d5e701","selectedAccounts":{"tiktok":["665f1b2a9c31a2b3c4d5e700"]},"schedule":"asap"}' ``` Response: ```json { "success": true, "data": { "id": "665f1b2a9c31a2b3c4d5e702", "kind": "post", "status": "scheduled", "renderStatus": "pending", "scheduledTime": "2026-07-02T10:05:00.000Z", "statusUrl": "/api/v1/posts/665f1b2a9c31a2b3c4d5e702" } } ``` ### List posts `GET /posts` Lists the workspace's posts with optional status, automation and scheduled-time filters. - Scopes: `posts:read` - Credits: none - CLI: `viraloop posts list` - MCP tool: `viraloop_list_posts` Query parameters: | Name | Type | Required | Description | | --- | --- | --- | --- | | `status` | `draft` \| `scheduled` \| `processing` \| `completed` \| `failed` \| `posted` \| `partial` | no | | | `automationId` | string | no | 24 character hex object id | | `from` | string | no | scheduledTime lower bound | | `to` | string | no | scheduledTime upper bound | | `page` | integer | no | Page number, starting at 1 | | `limit` | integer | no | Items per page (max 100) | | `workspaceId` | string | no | Workspace to operate in. Defaults to the team's default workspace. | Example: ```bash curl -s "https://viraloop.io/api/v1/posts" \ -H "Authorization: Bearer $VIRALOOP_API_KEY" ``` Response: ```json { "success": true, "data": [ { "id": "665f1b2a9c31a2b3c4d5e702", "postType": "video", "status": "posted", "renderStatus": "ready", "scheduledTime": "2026-07-02T10:05:00.000Z", "postedAt": "2026-07-02T10:06:03.000Z", "statusUrl": "/api/v1/posts/665f1b2a9c31a2b3c4d5e702" } ], "pagination": { "total": 1, "page": 1, "limit": 20, "pages": 1 } } ``` ### Get a post with per-platform results `GET /posts/{id}` Returns one post including render state and per-platform posting results (status, post URL, analytics snapshot). Poll this after creating a post: terminal statuses are posted, partial, completed and failed; renderStatus failed is also terminal. - Scopes: `posts:read` - Credits: none - Terminal states: `posted`, `partial`, `completed`, `failed` - CLI: `viraloop posts get ` - MCP tool: `viraloop_get_post` Example: ```bash curl -s "https://viraloop.io/api/v1/posts/" \ -H "Authorization: Bearer $VIRALOOP_API_KEY" ``` Response: ```json { "success": true, "data": { "id": "665f1b2a9c31a2b3c4d5e702", "postType": "video", "status": "posted", "renderStatus": "ready", "videoUrl": "https://cdn.viraloop.io/renders/abc.mp4", "platforms": [ { "platform": "tiktok", "status": "posted", "postUrl": "https://www.tiktok.com/@acme.hq/video/123", "analytics": { "views": 1200, "likes": 80, "comments": 4, "shares": 2 } } ] } } ``` ### Cancel a scheduled post `DELETE /posts/{id}` Cancels a post that has not been published yet (status scheduled). Published or in-flight posts cannot be cancelled; that returns 409. - Scopes: `posts:write` - Credits: none - CLI: `viraloop posts cancel ` - MCP tool: `viraloop_cancel_post` Example: ```bash curl -s -X DELETE "https://viraloop.io/api/v1/posts/" \ -H "Authorization: Bearer $VIRALOOP_API_KEY" ``` Response: ```json { "success": true, "data": { "id": "665f1b2a9c31a2b3c4d5e702", "status": "cancelled" } } ``` ### Posting calendar `GET /calendar` Returns the workspace's posts falling in a date window (by scheduled time, posted time, or creation time for drafts). Useful to check what is already queued before scheduling more. - Scopes: `posts:read` - Credits: none - CLI: `viraloop calendar` Query parameters: | Name | Type | Required | Description | | --- | --- | --- | --- | | `from` | string | yes | ISO 8601 date or datetime, e.g. 2026-07-03T10:00:00Z | | `to` | string | yes | ISO 8601 date or datetime, e.g. 2026-07-03T10:00:00Z | | `status` | `draft` \| `scheduled` \| `processing` \| `completed` \| `failed` \| `posted` \| `partial` | no | | | `workspaceId` | string | no | Workspace to operate in. Defaults to the team's default workspace. | Example: ```bash curl -s "https://viraloop.io/api/v1/calendar" \ -H "Authorization: Bearer $VIRALOOP_API_KEY" ``` Response: ```json { "success": true, "data": [ { "id": "665f1b2a9c31a2b3c4d5e702", "status": "scheduled", "scheduledTime": "2026-07-03T10:00:00.000Z", "postType": "video", "platforms": [ "tiktok" ] } ] } ``` ## automations ### Create an automation `POST /automations` Creates a draft automation: a batch of AI posts generated at once and published on a schedule. Set name, cadence, selectedAccounts and settings here or later via PATCH, then call generate, review the posts, and launch. Monthly automation quota depends on the plan. - Scopes: `automations:write` - Credits: none - Supports `Idempotency-Key` header - CLI: `viraloop automations create` - MCP tool: `viraloop_create_automation` Body fields: | Name | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | yes | | | `cadence` | object | no | Posting cadence: how many posts per day, for how many days | | `selectedAccounts` | object | no | Social account ids per platform (from GET /accounts). At least one non-empty platform array is required. | | `settings` | object | no | Generation settings (defaults are sensible; all optional) | | `ownerType` | `brand` \| `influencer` | no | | | `influencerId` | string | no | Required when ownerType is influencer | | `tiktokMode` | `direct` \| `inbox` | no | TikTok publish mode; inbox sends drafts to the inbox | | `tiktokOptions` | object | no | TikTok publish options (direct posting only) | | `workspaceId` | string | no | Workspace to operate in. Defaults to the team's default workspace. | Example: ```bash curl -s -X POST "https://viraloop.io/api/v1/automations" \ -H "Authorization: Bearer $VIRALOOP_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name":"July launch week","cadence":{"postsPerDay":2,"lengthDays":7,"timezone":"America/New_York"},"selectedAccounts":{"tiktok":["665f1b2a9c31a2b3c4d5e700"]}}' ``` Response: ```json { "success": true, "data": { "id": "665f1b2a9c31a2b3c4d5e710", "name": "July launch week", "status": "draft", "cadence": { "postsPerDay": 2, "lengthDays": 7, "timezone": "America/New_York", "timeSlots": [] }, "totalPosts": 0, "statusUrl": "/api/v1/automations/665f1b2a9c31a2b3c4d5e710" } } ``` ### List automations `GET /automations` Lists the workspace's automations plus the monthly quota (limit, used, remaining). - Scopes: `automations:read` - Credits: none - CLI: `viraloop automations list` - MCP tool: `viraloop_list_automations` Query parameters: | Name | Type | Required | Description | | --- | --- | --- | --- | | `status` | `draft` \| `generating` \| `review` \| `active` \| `completed` \| `cancelled` | no | | | `page` | integer | no | Page number, starting at 1 | | `limit` | integer | no | Items per page (max 100) | | `workspaceId` | string | no | Workspace to operate in. Defaults to the team's default workspace. | Example: ```bash curl -s "https://viraloop.io/api/v1/automations" \ -H "Authorization: Bearer $VIRALOOP_API_KEY" ``` Response: ```json { "success": true, "data": { "automations": [ { "id": "665f1b2a9c31a2b3c4d5e710", "name": "July launch week", "status": "active", "totalPosts": 14, "postedCount": 6 } ], "quota": { "monthlyLimit": 8, "usedThisMonth": 2, "remaining": 6 } }, "pagination": { "total": 2, "page": 1, "limit": 20, "pages": 1 } } ``` ### Get an automation `GET /automations/{id}` Returns one automation with its live generatedCount. Poll this after calling generate: the automation leaves the generating status when done (review on success). - Scopes: `automations:read` - Credits: none - Terminal states: `draft`, `review`, `active`, `completed`, `cancelled` - CLI: `viraloop automations get ` - MCP tool: `viraloop_get_automation` Example: ```bash curl -s "https://viraloop.io/api/v1/automations/" \ -H "Authorization: Bearer $VIRALOOP_API_KEY" ``` Response: ```json { "success": true, "data": { "id": "665f1b2a9c31a2b3c4d5e710", "name": "July launch week", "status": "review", "totalPosts": 14, "generatedCount": 14, "statusUrl": "/api/v1/automations/665f1b2a9c31a2b3c4d5e710" } } ``` ### Update a draft automation `PATCH /automations/{id}` Updates name, cadence, selectedAccounts, settings, ownerType, influencerId or tiktokMode. Only allowed while the automation is in draft or review; sub-objects are replaced wholesale. - Scopes: `automations:write` - Credits: none - CLI: `viraloop automations update ` Body fields: | Name | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | no | | | `cadence` | object | no | Posting cadence: how many posts per day, for how many days | | `selectedAccounts` | object | no | Social account ids per platform (from GET /accounts). At least one non-empty platform array is required. | | `settings` | object | no | Generation settings (defaults are sensible; all optional) | | `ownerType` | `brand` \| `influencer` | no | | | `influencerId` | string | no | 24 character hex object id | | `tiktokMode` | `direct` \| `inbox` | no | | | `tiktokOptions` | object | no | TikTok publish options (direct posting only) | Example: ```bash curl -s -X PATCH "https://viraloop.io/api/v1/automations/" \ -H "Authorization: Bearer $VIRALOOP_API_KEY" ``` Response: ```json { "success": true, "data": { "id": "665f1b2a9c31a2b3c4d5e710", "name": "July launch week", "status": "draft", "selectedAccounts": { "tiktok": [ "665f1b2a9c31a2b3c4d5e700" ] } } } ``` ### Generate the automation's posts `POST /automations/{id}/generate` Generates cadence.postsPerDay x cadence.lengthDays AI posts for the automation, each assigned a schedule slot. Asynchronous: returns 202 immediately; poll the automation until it reaches review (success) or back to draft (nothing generated). Calling it again while generating is a no-op that reports progress. Regenerating an automation in review replaces its posts. - Scopes: `automations:write` - Credits: none - Rate limit: 5 per 3600s - Terminal states: `draft`, `review`, `active`, `completed`, `cancelled` - CLI: `viraloop automations generate ` - MCP tool: `viraloop_generate_automation` Example: ```bash curl -s -X POST "https://viraloop.io/api/v1/automations//generate" \ -H "Authorization: Bearer $VIRALOOP_API_KEY" ``` Response: ```json { "success": true, "data": { "id": "665f1b2a9c31a2b3c4d5e710", "kind": "automation", "status": "generating", "totalPosts": 14, "generatedCount": 0, "statusUrl": "/api/v1/automations/665f1b2a9c31a2b3c4d5e710" } } ``` ### Extend an automation by more days `POST /automations/{id}/extend` Adds `days` more days to an active or completed automation: generates cadence.postsPerDay x days new posts, schedules them in the days after the current window and publishes them to the automation's accounts. Posts already scheduled or published are never touched. Extending a completed automation revives it to active. Asynchronous: returns 202 immediately; the automation carries extending=true until the new posts land, so poll GET /automations/{id} until it flips false. - Scopes: `automations:write` - Credits: none - Rate limit: 5 per 3600s - CLI: `viraloop automations extend --days ` - MCP tool: `viraloop_extend_automation` Body fields: | Name | Type | Required | Description | | --- | --- | --- | --- | | `days` | integer | yes | How many days to add to the automation window | | `workspaceId` | string | no | Workspace to operate in. Defaults to the team's default workspace. | Example: ```bash curl -s -X POST "https://viraloop.io/api/v1/automations//extend" \ -H "Authorization: Bearer $VIRALOOP_API_KEY" \ -H "Content-Type: application/json" \ -d '{"days":7}' ``` Response: ```json { "success": true, "data": { "id": "665f1b2a9c31a2b3c4d5e710", "kind": "automation", "status": "active", "extending": true, "adding": 7, "statusUrl": "/api/v1/automations/665f1b2a9c31a2b3c4d5e710" } } ``` ### Launch a reviewed automation `POST /automations/{id}/launch` Converts every generated post into a scheduled post at its slot and activates the automation. Requires status review and at least one selected account. Posts render server-side after this returns and publish automatically at their slots. - Scopes: `automations:write` - Credits: none - CLI: `viraloop automations launch ` - MCP tool: `viraloop_launch_automation` Example: ```bash curl -s -X POST "https://viraloop.io/api/v1/automations//launch" \ -H "Authorization: Bearer $VIRALOOP_API_KEY" ``` Response: ```json { "success": true, "data": { "id": "665f1b2a9c31a2b3c4d5e710", "kind": "automation", "status": "active", "scheduled": 14, "failed": 0, "statusUrl": "/api/v1/automations/665f1b2a9c31a2b3c4d5e710" } } ``` ### Cancel an automation `POST /automations/{id}/cancel` Cancels the automation and removes its unsent posts. Content that already went out stays live. Idempotent: cancelling a cancelled or completed automation returns cancelledPosts 0. - Scopes: `automations:write` - Credits: none - CLI: `viraloop automations cancel ` - MCP tool: `viraloop_cancel_automation` Example: ```bash curl -s -X POST "https://viraloop.io/api/v1/automations//cancel" \ -H "Authorization: Bearer $VIRALOOP_API_KEY" ``` Response: ```json { "success": true, "data": { "id": "665f1b2a9c31a2b3c4d5e710", "status": "cancelled", "cancelledPosts": 8 } } ``` ### List an automation's generated posts `GET /automations/{id}/posts` Lists the automation's generated posts (suggestions) in schedule order, for review before launching. Each carries caption, postCaption, hashtags, rationale and its assigned scheduledTime. - Scopes: `automations:read` - Credits: none - CLI: `viraloop automations posts ` - MCP tool: `viraloop_list_automation_posts` Example: ```bash curl -s "https://viraloop.io/api/v1/automations//posts" \ -H "Authorization: Bearer $VIRALOOP_API_KEY" ``` Response: ```json { "success": true, "data": [ { "id": "665f1b2a9c31a2b3c4d5e701", "format": "walloftext", "caption": "POV: the client asks for one small change", "postCaption": "Every designer knows this feeling", "hashtags": [ "design" ], "status": "pending", "scheduledTime": "2026-07-06T13:00:00.000Z" } ] } ``` ## influencers ### List AI influencers `GET /influencers` Lists the workspace's AI influencers (virtual personas used to front content). - Scopes: `influencers:read` - Credits: none - CLI: `viraloop influencers list` - MCP tool: `viraloop_list_influencers` Query parameters: | Name | Type | Required | Description | | --- | --- | --- | --- | | `search` | string | no | Search name/description/tags | | `niche` | string | no | | | `page` | integer | no | Page number, starting at 1 | | `limit` | integer | no | Items per page (max 100) | | `workspaceId` | string | no | Workspace to operate in. Defaults to the team's default workspace. | Example: ```bash curl -s "https://viraloop.io/api/v1/influencers" \ -H "Authorization: Bearer $VIRALOOP_API_KEY" ``` Response: ```json { "success": true, "data": [ { "id": "665f1b2a9c31a2b3c4d5e720", "name": "Maya", "niche": "fitness", "style": "realistic", "imageUrl": "https://cdn.viraloop.io/influencers/maya.jpg", "turboEnabled": true } ], "pagination": { "total": 3, "page": 1, "limit": 20, "pages": 1 } } ``` ### Create an AI influencer `POST /influencers` Creates an influencer from a base image you provide (a publicly reachable image URL). A short animated preview is generated in the background when the team has credits. To generate a base image from a prompt instead, use the web app. Set style to "character" for a non-human influencer (a mascot, cartoon or stick figure): every prompt the platform builds for it then describes a stylized character instead of a real person, and gender/age/ethnicity do not apply. - Scopes: `influencers:write` - Credits: 10 credits for the optional animated preview (skipped when out of credits) - Rate limit: 10 per 3600s - Supports `Idempotency-Key` header - CLI: `viraloop influencers create` - MCP tool: `viraloop_create_influencer` Body fields: | Name | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | yes | | | `imageUrl` | string | yes | Publicly reachable base image of the persona | | `description` | string | no | | | `niche` | string | no | One of the app's niche slugs, e.g. fitness, tech, food | | `style` | `realistic` \| `character` | no | Visual style. "realistic" (default) is a photoreal human; "character" is a stylized non-human persona. | | `characterDescription` | string | no | For style "character": what the character looks like, including its art style. Used to keep later generations on-model. | | `gender` | `male` \| `female` | no | Only meaningful for style "realistic". | | `age` | integer | no | Only meaningful for style "realistic". | | `ethnicity` | string | no | Only meaningful for style "realistic". | | `tags` | array | no | | | `workspaceId` | string | no | Workspace to operate in. Defaults to the team's default workspace. | Example: ```bash curl -s -X POST "https://viraloop.io/api/v1/influencers" \ -H "Authorization: Bearer $VIRALOOP_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name":"Maya","imageUrl":"https://example.com/maya.jpg","niche":"fitness","gender":"female"}' ``` Response: ```json { "success": true, "data": { "id": "665f1b2a9c31a2b3c4d5e720", "name": "Maya", "niche": "fitness", "style": "realistic", "imageUrl": "https://example.com/maya.jpg", "videoPreview": { "status": "pending", "videoUrl": "", "thumbnailUrl": "" } } } ``` ### Get an influencer `GET /influencers/{id}` Returns one influencer including its base image, preview video state and Turbo settings. - Scopes: `influencers:read` - Credits: none - CLI: `viraloop influencers get ` Example: ```bash curl -s "https://viraloop.io/api/v1/influencers/" \ -H "Authorization: Bearer $VIRALOOP_API_KEY" ``` Response: ```json { "success": true, "data": { "id": "665f1b2a9c31a2b3c4d5e720", "name": "Maya", "description": "Energetic fitness coach", "niche": "fitness", "style": "realistic", "imageUrl": "https://cdn.viraloop.io/influencers/maya.jpg", "turboEnabled": true, "allowedAngles": [] } } ``` ## videos ### Generate a talking-head video `POST /influencers/{id}/videos` Generates a talking-head UGC video of the influencer speaking your script (Seedance 2, 9:16). Costs 5 credits per second of video (default 10s = 50 credits), deducted up front and refunded automatically if generation fails. Insufficient credits returns HTTP 402 (insufficient_credits). Asynchronous: returns 202 with the video in processing; poll GET /videos/{videoId} until completed or failed (typically 2 to 10 minutes). - Scopes: `influencers:write` - Credits: 5 credits per second (duration 5-30s; default 10s = 50 credits) - Rate limit: 10 per 600s - Supports `Idempotency-Key` header - Terminal states: `completed`, `failed` - CLI: `viraloop videos create --influencer ` - MCP tool: `viraloop_create_video` Body fields: | Name | Type | Required | Description | | --- | --- | --- | --- | | `script` | string | yes | What the influencer says (max 1000 chars) | | `imageUrl` | string | no | Reference image; defaults to the influencer's base image | | `language` | string | no | Spoken language, default English | | `duration` | integer | no | | | `mode` | `speaker` \| `scene` | no | speaker: talking head; scene: wider shot | Example: ```bash curl -s -X POST "https://viraloop.io/api/v1/influencers//videos" \ -H "Authorization: Bearer $VIRALOOP_API_KEY" \ -H "Content-Type: application/json" \ -d '{"script":"Three things I wish I knew before my first marathon","duration":10}' ``` Response: ```json { "success": true, "data": { "id": "665f1b2a9c31a2b3c4d5e730", "kind": "video", "influencerId": "665f1b2a9c31a2b3c4d5e720", "status": "processing", "model": "seedance_2", "creditsUsed": 50, "statusUrl": "/api/v1/videos/665f1b2a9c31a2b3c4d5e730" } } ``` ### List an influencer's videos `GET /influencers/{id}/videos` Lists the influencer's generated videos, newest first, with status and URLs. - Scopes: `influencers:read` - Credits: none - CLI: `viraloop videos list --influencer ` Query parameters: | Name | Type | Required | Description | | --- | --- | --- | --- | | `status` | `pending` \| `processing` \| `completed` \| `failed` | no | | | `page` | integer | no | Page number, starting at 1 | | `limit` | integer | no | Items per page (max 100) | Example: ```bash curl -s "https://viraloop.io/api/v1/influencers//videos" \ -H "Authorization: Bearer $VIRALOOP_API_KEY" ``` Response: ```json { "success": true, "data": [ { "id": "665f1b2a9c31a2b3c4d5e730", "status": "completed", "videoUrl": "https://cdn.viraloop.io/videos/abc.mp4", "thumbnailUrl": "https://cdn.viraloop.io/videos/abc.jpg" } ], "pagination": { "total": 1, "page": 1, "limit": 20, "pages": 1 } } ``` ### Get a video's status `GET /videos/{id}` Returns one generated video. Poll this after creating a video: terminal statuses are completed (videoUrl set) and failed (error set). A completed video can be posted via POST /posts with videoUrl. - Scopes: `influencers:read` - Credits: none - Terminal states: `completed`, `failed` - CLI: `viraloop videos get ` - MCP tool: `viraloop_get_video` Example: ```bash curl -s "https://viraloop.io/api/v1/videos/" \ -H "Authorization: Bearer $VIRALOOP_API_KEY" ``` Response: ```json { "success": true, "data": { "id": "665f1b2a9c31a2b3c4d5e730", "status": "completed", "videoUrl": "https://cdn.viraloop.io/videos/abc.mp4", "thumbnailUrl": "https://cdn.viraloop.io/videos/abc.jpg", "completedAt": "2026-07-02T12:08:00.000Z" } } ``` ## assets ### List media assets `GET /assets` Lists the workspace's uploaded media library (videos and images used as generation backgrounds and sources). Uploading happens in the web app in v1. - Scopes: `assets:read` - Credits: none - CLI: `viraloop assets list` - MCP tool: `viraloop_list_assets` Query parameters: | Name | Type | Required | Description | | --- | --- | --- | --- | | `kind` | `video` \| `image` \| `audio` | no | | | `category` | string | no | | | `turboEnabled` | boolean | no | | | `page` | integer | no | Page number, starting at 1 | | `limit` | integer | no | Items per page (max 100) | | `workspaceId` | string | no | Workspace to operate in. Defaults to the team's default workspace. | Example: ```bash curl -s "https://viraloop.io/api/v1/assets" \ -H "Authorization: Bearer $VIRALOOP_API_KEY" ``` Response: ```json { "success": true, "data": [ { "id": "665f1b2a9c31a2b3c4d5e740", "kind": "video", "category": "wall-of-text", "name": "gym-broll.mp4", "url": "https://cdn.viraloop.io/assets/gym-broll.mp4", "turboEnabled": true } ], "pagination": { "total": 12, "page": 1, "limit": 20, "pages": 1 } } ``` ### Upload media into the workspace `POST /assets` Ingests a publicly reachable file into the workspace's media bank and returns a stable hosted asset. Use this to get a source video, a character photo or an app screenshot into Viraloop before generating a studio format that needs one. The source URL is fetched server-side, so it must be publicly readable (no auth headers, no signed-cookie hosts) and stay up until the request returns. Synchronous. - Scopes: `assets:write` - Credits: none - Rate limit: 30 per 300s - Supports `Idempotency-Key` header - CLI: `viraloop assets upload --url ` - MCP tool: `viraloop_upload_asset` Body fields: | Name | Type | Required | Description | | --- | --- | --- | --- | | `url` | string | yes | Publicly reachable https URL of the file to ingest | | `kind` | `video` \| `image` \| `audio` | no | video, image or audio. Inferred from the response content-type when omitted. | | `name` | string | no | Display name in the media bank | | `category` | string | no | Media Bank category | | `workspaceId` | string | no | Workspace to operate in. Defaults to the team's default workspace. | Example: ```bash curl -s -X POST "https://viraloop.io/api/v1/assets" \ -H "Authorization: Bearer $VIRALOOP_API_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com/clips/reaction.mp4","kind":"video","name":"reaction.mp4"}' ``` Response: ```json { "success": true, "data": { "id": "665f1b2a9c31a2b3c4d5e741", "kind": "video", "name": "reaction.mp4", "url": "https://cdn.viraloop.io/assets/reaction.mp4" } } ``` ## content ### Swap the character in a video `POST /content/character-swap` Replaces the person in a source clip with your character, driven by the clip's motion (Kling motion control, 9:16). Both media URLs must be hosted; upload them with POST /assets first. Costs 2 credits per second of source video, plus 3 credits when scene is 'recreate' (an extra image edit that composites your character into the clip's opening frame). Credits are deducted up front and refunded automatically if generation fails; insufficient credits returns HTTP 402. Asynchronous: returns 202, then poll GET /content/{id} until ready or failed (typically 3 to 20 minutes). - Scopes: `generations:write` - Credits: 2 credits per second of source video, +3 when scene is 'recreate' - Rate limit: 10 per 300s - Supports `Idempotency-Key` header - Terminal states: `ready`, `failed` - CLI: `viraloop content character-swap --video --character ` - MCP tool: `viraloop_create_character_swap` Body fields: | Name | Type | Required | Description | | --- | --- | --- | --- | | `videoUrl` | string | yes | Hosted source clip, 3-30 seconds. Its length sets the output length and price. | | `characterImageUrl` | string | yes | Hosted photo of the character to swap in; should clearly show one face | | `scene` | `image` \| `recreate` | no | image (default): keep the character photo's own background, borrow only the clip's motion. recreate: composite the character into the clip's first frame instead, keeping the clip's scene but a still background. | | `keepSourceAudio` | boolean | no | Lay the clip's original audio over the result. Default true. | | `influencerId` | string | no | Link the result to this influencer's gallery | | `name` | string | no | | | `workspaceId` | string | no | Workspace to operate in. Defaults to the team's default workspace. | Example: ```bash curl -s -X POST "https://viraloop.io/api/v1/content/character-swap" \ -H "Authorization: Bearer $VIRALOOP_API_KEY" \ -H "Content-Type: application/json" \ -d '{"videoUrl":"https://cdn.viraloop.io/assets/reaction.mp4","characterImageUrl":"https://cdn.viraloop.io/assets/maya.jpg","scene":"image"}' ``` Response: ```json { "success": true, "data": { "id": "665f1b2a9c31a2b3c4d5e750", "format": "Character Swap", "status": "processing", "creditsUsed": 20 } } ``` ### Generate a presenter holding your app `POST /content/green-screen-mobile` Generates a UGC video of a presenter holding a phone with your app on screen, speaking your script (9:16). Runs two steps server-side: one image edit puts the phone in the presenter's hand, then Seedance animates it. Both media URLs must be hosted; upload them with POST /assets first. Costs 3 credits for the composite plus 5 credits per second of video (default 8s = 43 credits), deducted up front and refunded automatically on failure; insufficient credits returns HTTP 402. Asynchronous: returns 202, then poll GET /content/{id} until ready or failed (typically 2 to 10 minutes). - Scopes: `generations:write` - Credits: 3 credits + 5 credits per second (duration 4-15s; default 8s = 43 credits) - Rate limit: 10 per 300s - Supports `Idempotency-Key` header - Terminal states: `ready`, `failed` - CLI: `viraloop content green-screen-mobile --screenshot --presenter ` - MCP tool: `viraloop_create_green_screen_mobile` Body fields: | Name | Type | Required | Description | | --- | --- | --- | --- | | `appScreenshot` | string | yes | Hosted screenshot of your app; it is composited onto the phone screen as-is | | `presenterImage` | string | yes | Hosted photo of the presenter; should clearly show their face | | `script` | string | yes | What the presenter says about your app (max 1000 chars) | | `duration` | integer | no | Seconds, default 8 | | `language` | string | no | Spoken language, default English | | `captionOverlay` | boolean | no | Transcribe the speech into a styled caption track. Default true. | | `influencerId` | string | no | Link the result to this influencer | | `name` | string | no | | | `workspaceId` | string | no | Workspace to operate in. Defaults to the team's default workspace. | Example: ```bash curl -s -X POST "https://viraloop.io/api/v1/content/green-screen-mobile" \ -H "Authorization: Bearer $VIRALOOP_API_KEY" \ -H "Content-Type: application/json" \ -d '{"appScreenshot":"https://cdn.viraloop.io/assets/app-home.png","presenterImage":"https://cdn.viraloop.io/assets/maya.jpg","script":"This is the fastest way to ship short-form video.","duration":8}' ``` Response: ```json { "success": true, "data": { "id": "665f1b2a9c31a2b3c4d5e751", "format": "green-screen-mobile", "status": "processing", "creditsUsed": 43 } } ``` ### Clone a video's motion onto your character `POST /content/clone-video` Drives your character with the motion of a reference clip (Kling motion control, 9:16). Unlike character swap this keeps your character's own scene throughout; it borrows only the movement. Both media URLs must be hosted; upload them with POST /assets first. Costs 2 credits per second of reference video, deducted up front and refunded automatically on failure; insufficient credits returns HTTP 402. Asynchronous: returns 202, then poll GET /content/{id} until ready or failed (typically 3 to 20 minutes). - Scopes: `generations:write` - Credits: 2 credits per second of reference video - Rate limit: 10 per 300s - Supports `Idempotency-Key` header - Terminal states: `ready`, `failed` - CLI: `viraloop content clone-video --image --video ` - MCP tool: `viraloop_create_clone_video` Body fields: | Name | Type | Required | Description | | --- | --- | --- | --- | | `imageUrl` | string | yes | Hosted photo of the character to animate | | `videoUrl` | string | yes | Hosted reference clip whose motion is copied. Its length sets the price. | | `videoDuration` | number | no | Reference clip length in seconds. Probed from the file when omitted. | | `influencerId` | string | no | Also save the result to this influencer's gallery | | `name` | string | no | | | `workspaceId` | string | no | Workspace to operate in. Defaults to the team's default workspace. | Example: ```bash curl -s -X POST "https://viraloop.io/api/v1/content/clone-video" \ -H "Authorization: Bearer $VIRALOOP_API_KEY" \ -H "Content-Type: application/json" \ -d '{"imageUrl":"https://cdn.viraloop.io/assets/maya.jpg","videoUrl":"https://cdn.viraloop.io/assets/dance.mp4"}' ``` Response: ```json { "success": true, "data": { "id": "665f1b2a9c31a2b3c4d5e751", "format": "Clone Video", "status": "processing", "statusUrl": "/api/v1/content/665f1b2a9c31a2b3c4d5e751" } } ``` ### Generate a talking-head UGC video `POST /content/talking-head` Generates a UGC video of a person speaking your script to camera (Seedance, 9:16, with voice). Describe the person with gender/age/ethnicity/appearance, or pin their exact likeness with avatarImageUrl (a hosted photo; upload one with POST /assets). Costs 5 credits per second (default 10s = 50 credits), deducted up front and refunded automatically on failure; insufficient credits returns HTTP 402. Asynchronous: returns 202, then poll GET /content/{id} until ready or failed (typically 2 to 10 minutes). - Scopes: `generations:write` - Credits: 5 credits per second (duration 4-15s; default 10s = 50 credits) - Rate limit: 20 per 300s - Supports `Idempotency-Key` header - Terminal states: `ready`, `failed` - CLI: `viraloop content talking-head --script ` - MCP tool: `viraloop_create_talking_head` Body fields: | Name | Type | Required | Description | | --- | --- | --- | --- | | `script` | string | yes | What the person says (max 1000 chars) | | `avatarImageUrl` | string | no | Hosted photo pinning the speaker's likeness. Overrides the persona fields. | | `duration` | integer | no | Seconds, default 10 | | `language` | string | no | Spoken language, default English | | `mode` | `speaker` \| `scene` | no | speaker (default): head-and-shoulders to camera. scene: a wider shot. | | `gender` | string | no | Persona hint, ignored when avatarImageUrl is set | | `age` | string | no | Persona hint, ignored when avatarImageUrl is set | | `ethnicity` | string | no | Persona hint, ignored when avatarImageUrl is set | | `appearance` | string | no | Free-text look, ignored when avatarImageUrl is set | | `referenceImages` | array | no | Extra hosted reference images (props, setting) | | `captionOverlay` | boolean | no | Transcribe the speech into a styled caption track. Default true. | | `influencerId` | string | no | Link the result to this influencer | | `name` | string | no | | | `workspaceId` | string | no | Workspace to operate in. Defaults to the team's default workspace. | Example: ```bash curl -s -X POST "https://viraloop.io/api/v1/content/talking-head" \ -H "Authorization: Bearer $VIRALOOP_API_KEY" \ -H "Content-Type: application/json" \ -d '{"script":"I stopped writing captions by hand and my reach doubled.","duration":10,"gender":"woman","age":"20s"}' ``` Response: ```json { "success": true, "data": { "id": "665f1b2a9c31a2b3c4d5e752", "format": "Talking Head UGC", "status": "processing", "statusUrl": "/api/v1/content/665f1b2a9c31a2b3c4d5e752" } } ``` ### Generate a presenter over your demo video `POST /content/talking-head-green-screen` Generates a presenter speaking your script on a green screen, then composites them into the corner of your demo video at render time (9:16). Both media URLs must be hosted; upload them with POST /assets first. Costs 5 credits per second (default 10s = 50 credits), deducted up front and refunded automatically on failure; insufficient credits returns HTTP 402. Asynchronous: returns 202, then poll GET /content/{id} until ready or failed (typically 2 to 15 minutes). - Scopes: `generations:write` - Credits: 5 credits per second (duration 4-15s; default 10s = 50 credits) - Rate limit: 20 per 300s - Supports `Idempotency-Key` header - Terminal states: `ready`, `failed` - CLI: `viraloop content talking-head-green-screen --script --avatar --demo ` - MCP tool: `viraloop_create_talking_head_green_screen` Body fields: | Name | Type | Required | Description | | --- | --- | --- | --- | | `script` | string | yes | What the presenter says (max 1000 chars) | | `avatarImageUrl` | string | yes | Hosted photo of the presenter | | `demoVideoUrl` | string | yes | Hosted demo/screen-recording that plays behind | | `demoThumb` | string | no | Poster image for the demo video | | `avatarPosition` | `bottom-left` \| `bottom-right` | no | Which corner the presenter sits in. Default bottom-right. | | `duration` | integer | no | Seconds, default 10 | | `language` | string | no | Spoken language, default English | | `captionOverlay` | boolean | no | Transcribe the speech into a styled caption track. Default false. | | `influencerId` | string | no | Link the result to this influencer | | `name` | string | no | | | `workspaceId` | string | no | Workspace to operate in. Defaults to the team's default workspace. | Example: ```bash curl -s -X POST "https://viraloop.io/api/v1/content/talking-head-green-screen" \ -H "Authorization: Bearer $VIRALOOP_API_KEY" \ -H "Content-Type: application/json" \ -d '{"script":"Here is how the scheduler actually works.","avatarImageUrl":"https://cdn.viraloop.io/assets/maya.jpg","demoVideoUrl":"https://cdn.viraloop.io/assets/product-demo.mp4"}' ``` Response: ```json { "success": true, "data": { "id": "665f1b2a9c31a2b3c4d5e753", "format": "Talking Head Green Screen", "status": "processing", "statusUrl": "/api/v1/content/665f1b2a9c31a2b3c4d5e753" } } ``` ### Generate an interview (podcast clip) video `POST /content/interview` Generates a cinematic podcast-clip video: an AI guest in a studio interview setting (broadcast mic, moody lighting) speaking your script as a candid answer (Seedance, 9:16, with voice). Describe the person with gender/age/ethnicity/appearance, or pin their exact likeness with avatarImageUrl (a hosted photo; upload one with POST /assets). Costs 5 credits per second (default 10s = 50 credits), deducted up front and refunded automatically on failure; insufficient credits returns HTTP 402. Asynchronous: returns 202, then poll GET /content/{id} until ready or failed (typically 2 to 10 minutes). - Scopes: `generations:write` - Credits: 5 credits per second (duration 4-15s; default 10s = 50 credits) - Rate limit: 20 per 300s - Supports `Idempotency-Key` header - Terminal states: `ready`, `failed` - CLI: `viraloop content interview --script ` - MCP tool: `viraloop_create_interview` Body fields: | Name | Type | Required | Description | | --- | --- | --- | --- | | `script` | string | yes | What the guest says, written as a candid answer (max 1000 chars) | | `avatarImageUrl` | string | no | Hosted photo pinning the guest's likeness. Overrides the persona fields. | | `duration` | integer | no | Seconds, default 10 | | `language` | string | no | Spoken language, default English | | `gender` | string | no | Persona hint, ignored when avatarImageUrl is set | | `age` | string | no | Persona hint, ignored when avatarImageUrl is set | | `ethnicity` | string | no | Persona hint, ignored when avatarImageUrl is set | | `appearance` | string | no | Free-text look, ignored when avatarImageUrl is set | | `captionOverlay` | boolean | no | Transcribe the speech into a styled caption track. Default true. | | `influencerId` | string | no | Link the result to this influencer | | `name` | string | no | | | `workspaceId` | string | no | Workspace to operate in. Defaults to the team's default workspace. | Example: ```bash curl -s -X POST "https://viraloop.io/api/v1/content/interview" \ -H "Authorization: Bearer $VIRALOOP_API_KEY" \ -H "Content-Type: application/json" \ -d '{"script":"Everyone thinks I got lucky. The truth is I automated the boring half of my content.","duration":10,"gender":"man","age":"30s"}' ``` Response: ```json { "success": true, "data": { "id": "665f1b2a9c31a2b3c4d5e75b", "format": "Interview", "status": "processing", "statusUrl": "/api/v1/content/665f1b2a9c31a2b3c4d5e75b" } } ``` ### Generate a spokesperson holding your product `POST /content/product-spokesperson` Generates a UGC video of a person holding your product and talking about it (Seedance, 9:16, with voice). Give either spokespersonImage (a shot of someone already holding it) or avatarImage plus productImage, which are composed into one first. All media URLs must be hosted; upload them with POST /assets first. Costs 5 credits per second (default 8s = 40 credits) plus 3 credits when we compose the shot, deducted up front and refunded automatically on failure; insufficient credits returns HTTP 402. Asynchronous: returns 202, then poll GET /content/{id} until ready or failed (typically 2 to 10 minutes). - Scopes: `generations:write` - Credits: 5 credits per second (duration 4-15s; default 8s = 40 credits), +3 when composing the shot - Rate limit: 20 per 300s - Supports `Idempotency-Key` header - Terminal states: `ready`, `failed` - CLI: `viraloop content product-spokesperson --script --product ` - MCP tool: `viraloop_create_product_spokesperson` Body fields: | Name | Type | Required | Description | | --- | --- | --- | --- | | `script` | string | yes | What the spokesperson says (max 1000 chars) | | `spokespersonImage` | string | no | Hosted photo of a person already holding the product | | `avatarImage` | string | no | Hosted photo of the person, composed with productImage | | `productImage` | string | no | Hosted photo of the product, composed with avatarImage | | `instruction` | string | no | Extra direction for the composed shot, e.g. 'outdoors, morning light' | | `duration` | integer | no | Seconds, default 8 | | `language` | string | no | Spoken language, default English | | `captionOverlay` | boolean | no | Transcribe the speech into a styled caption track. Default true. | | `influencerId` | string | no | Link the result to this influencer | | `name` | string | no | | | `workspaceId` | string | no | Workspace to operate in. Defaults to the team's default workspace. | Example: ```bash curl -s -X POST "https://viraloop.io/api/v1/content/product-spokesperson" \ -H "Authorization: Bearer $VIRALOOP_API_KEY" \ -H "Content-Type: application/json" \ -d '{"script":"This is the only bottle I take to the gym now.","avatarImage":"https://cdn.viraloop.io/assets/maya.jpg","productImage":"https://cdn.viraloop.io/assets/bottle.png"}' ``` Response: ```json { "success": true, "data": { "id": "665f1b2a9c31a2b3c4d5e754", "format": "Product Spokesperson", "status": "processing", "statusUrl": "/api/v1/content/665f1b2a9c31a2b3c4d5e754" } } ``` ### Create a 2x2 grid video `POST /content/grid-video` Writes a listicle heading plus four labelled cells from your brand and prompt, finds a stock photo for each cell, and saves the result to your library (9:16). Costs no credits. Synchronous: the request returns when the deck is built, typically 10 to 40 seconds. Publish it with POST /posts using the deck from GET /content/{id}, or open it in the studio to edit first. - Scopes: `generations:write` - Credits: none - Rate limit: 20 per 300s - Supports `Idempotency-Key` header - CLI: `viraloop content grid-video --prompt ` - MCP tool: `viraloop_create_grid_video` Body fields: | Name | Type | Required | Description | | --- | --- | --- | --- | | `prompt` | string | no | What the grid is about. Omit to let the model pick from your brand. | | `mentionBusiness` | boolean | no | Make one of the four cells your brand. Default true. | | `portrait` | boolean | no | Use portrait cell photos instead of landscape. Default false. | | `name` | string | no | | | `workspaceId` | string | no | Workspace to operate in. Defaults to the team's default workspace. | Example: ```bash curl -s -X POST "https://viraloop.io/api/v1/content/grid-video" \ -H "Authorization: Bearer $VIRALOOP_API_KEY" \ -H "Content-Type: application/json" \ -d '{"prompt":"tools every solo founder should be using"}' ``` Response: ```json { "success": true, "data": { "id": "665f1b2a9c31a2b3c4d5e755", "format": "2x2 Grid Video", "status": "ready", "title": "tools every solo founder should be using:" } } ``` ### Create a listicle video `POST /content/listicle` Writes a numbered-list title plus 4 to 6 items from your brand and prompt, lays them over a stock UGC background clip that reveals one item per beat, and saves the result to your library (9:16). Costs no credits. Synchronous: the request returns when the deck is built, typically 5 to 20 seconds. Publish it with POST /posts using the deck from GET /content/{id}, or open it in the studio to edit first. - Scopes: `generations:write` - Credits: none - Rate limit: 20 per 300s - Supports `Idempotency-Key` header - CLI: `viraloop content listicle --prompt ` - MCP tool: `viraloop_create_listicle` Body fields: | Name | Type | Required | Description | | --- | --- | --- | --- | | `prompt` | string | no | What the list is about. Omit to let the model pick from your brand. | | `mentionBusiness` | boolean | no | Make one of the items your brand. Default true. | | `layout` | `list` \| `pyramid` \| `checklist` \| `countdown` | no | Display style: numbered list (default), tier pyramid, checklist, or countdown to #1. | | `name` | string | no | | | `workspaceId` | string | no | Workspace to operate in. Defaults to the team's default workspace. | Example: ```bash curl -s -X POST "https://viraloop.io/api/v1/content/listicle" \ -H "Authorization: Bearer $VIRALOOP_API_KEY" \ -H "Content-Type: application/json" \ -d '{"prompt":"habits of founders who ship every week"}' ``` Response: ```json { "success": true, "data": { "id": "665f1b2a9c31a2b3c4d5e757", "format": "Listicle", "status": "ready", "title": "5 habits of founders who ship every week:" } } ``` ### Create an Ask Me Anything video `POST /content/ask-me-anything` Writes the question your audience actually asks plus the on-video answer from your brand and prompt, lays them over a stock UGC background clip as an IG-style question sticker, and saves the result to your library (9:16). Costs no credits. Synchronous: the request returns when the deck is built, typically 5 to 20 seconds. Publish it with POST /posts using the deck from GET /content/{id}, or open it in the studio to edit first. - Scopes: `generations:write` - Credits: none - Rate limit: 20 per 300s - Supports `Idempotency-Key` header - CLI: `viraloop content ask-me-anything --prompt ` - MCP tool: `viraloop_create_ask_me_anything` Body fields: | Name | Type | Required | Description | | --- | --- | --- | --- | | `prompt` | string | no | What the question should be about. Omit to let the model pick from your brand. | | `mentionBusiness` | boolean | no | Name the brand in the answer. Default true. | | `name` | string | no | | | `workspaceId` | string | no | Workspace to operate in. Defaults to the team's default workspace. | Example: ```bash curl -s -X POST "https://viraloop.io/api/v1/content/ask-me-anything" \ -H "Authorization: Bearer $VIRALOOP_API_KEY" \ -H "Content-Type: application/json" \ -d '{"prompt":"how I stay consistent posting every day"}' ``` Response: ```json { "success": true, "data": { "id": "665f1b2a9c31a2b3c4d5e758", "format": "Ask Me Anything", "status": "ready", "title": "how do you post every single day?" } } ``` ### Create a ranking (tier list) video `POST /content/ranking` Writes a tier-list title plus 5 to 7 tiered items (S/A/B/C) from your brand and prompt, finds a stock photo for each item, and saves an image tier-board video to your library (9:16, items reveal one per beat). Costs no credits. Synchronous: the request returns when the deck is built, typically 10 to 40 seconds. Publish it with POST /posts using the deck from GET /content/{id}, or open it in the studio to edit first. - Scopes: `generations:write` - Credits: none - Rate limit: 20 per 300s - Supports `Idempotency-Key` header - CLI: `viraloop content ranking --prompt ` - MCP tool: `viraloop_create_ranking` Body fields: | Name | Type | Required | Description | | --- | --- | --- | --- | | `prompt` | string | no | What the ranking is about. Omit to let the model pick from your brand. | | `mentionBusiness` | boolean | no | Make one of the items your brand (tier S, revealed last). Default true. | | `name` | string | no | | | `workspaceId` | string | no | Workspace to operate in. Defaults to the team's default workspace. | Example: ```bash curl -s -X POST "https://viraloop.io/api/v1/content/ranking" \ -H "Authorization: Bearer $VIRALOOP_API_KEY" \ -H "Content-Type: application/json" \ -d '{"prompt":"ranking the ways to grow on tiktok"}' ``` Response: ```json { "success": true, "data": { "id": "665f1b2a9c31a2b3c4d5e759", "format": "Ranking", "status": "ready", "title": "ranking the ways to grow on tiktok:" } } ``` ### Create a split screen video `POST /content/split-screen` Writes an on-brand caption from your prompt (or uses the caption you pass), stacks a stock UGC content clip on top of a looping gameplay/satisfying clip, and saves the result to your library (9:16, duration follows the top clip). Costs no credits. Synchronous: the request returns when the deck is built, typically 5 to 15 seconds. Publish it with POST /posts using the deck from GET /content/{id}, or open it in the studio to edit first. - Scopes: `generations:write` - Credits: none - Rate limit: 20 per 300s - Supports `Idempotency-Key` header - CLI: `viraloop content split-screen --prompt ` - MCP tool: `viraloop_create_split_screen` Body fields: | Name | Type | Required | Description | | --- | --- | --- | --- | | `prompt` | string | no | What the caption should be about. Omit to let the model pick from your brand. | | `caption` | string | no | Use this exact on-video caption instead of generating one. | | `mentionBusiness` | boolean | no | Name the brand in the caption. Default true. | | `name` | string | no | | | `workspaceId` | string | no | Workspace to operate in. Defaults to the team's default workspace. | Example: ```bash curl -s -X POST "https://viraloop.io/api/v1/content/split-screen" \ -H "Authorization: Bearer $VIRALOOP_API_KEY" \ -H "Content-Type: application/json" \ -d '{"prompt":"POV: you found the tool that edits for you"}' ``` Response: ```json { "success": true, "data": { "id": "665f1b2a9c31a2b3c4d5e75a", "format": "Split Screen", "status": "ready", "title": "POV: you found the tool that edits for you" } } ``` ### Create a single fade-in video `POST /content/fade-in-video` Writes one bold caption from your brand and prompt, pairs it with a stock photo that fades in, and saves the result to your library (9:16). Pass imageUrl to use your own picture instead. Costs no credits. Synchronous: the request returns when the deck is built, typically 10 to 30 seconds. - Scopes: `generations:write` - Credits: none - Rate limit: 20 per 300s - Supports `Idempotency-Key` header - CLI: `viraloop content fade-in-video --prompt ` - MCP tool: `viraloop_create_fade_in_video` Body fields: | Name | Type | Required | Description | | --- | --- | --- | --- | | `prompt` | string | no | What the video is about. Omit to let the model pick from your brand. | | `imageUrl` | string | no | Hosted image to fade in. A stock photo is found when omitted. | | `mentionBusiness` | boolean | no | Name the brand in the copy. Default true. | | `name` | string | no | | | `workspaceId` | string | no | Workspace to operate in. Defaults to the team's default workspace. | Example: ```bash curl -s -X POST "https://viraloop.io/api/v1/content/fade-in-video" \ -H "Authorization: Bearer $VIRALOOP_API_KEY" \ -H "Content-Type: application/json" \ -d '{"prompt":"the real reason your reach dropped"}' ``` Response: ```json { "success": true, "data": { "id": "665f1b2a9c31a2b3c4d5e756", "format": "Single Fade-in Video", "status": "ready", "title": "the real reason your reach dropped" } } ``` ### Stitch a hook clip onto your demo `POST /content/hook-demo` Puts a captioned hook clip in front of your product demo and saves the pair to your library (9:16). Both media URLs must be hosted; upload them with POST /assets first. The on-screen line is written from your brand unless you pass caption. Costs no credits. Synchronous: returns when the deck is built, typically 5 to 30 seconds. - Scopes: `generations:write` - Credits: none - Rate limit: 20 per 300s - Supports `Idempotency-Key` header - CLI: `viraloop content hook-demo --hook --demo ` - MCP tool: `viraloop_create_hook_demo` Body fields: | Name | Type | Required | Description | | --- | --- | --- | --- | | `hookVideoUrl` | string | yes | Hosted attention-grabbing clip that plays first | | `demoVideoUrl` | string | yes | Hosted product demo that plays after the hook | | `caption` | string | no | The line burned over the hook. Written from your brand when omitted. | | `prompt` | string | no | What the caption should be about, when caption is omitted | | `hookThumb` | string | no | Poster image for the hook clip | | `mentionBusiness` | boolean | no | Name the brand in the copy. Default true. | | `name` | string | no | | | `workspaceId` | string | no | Workspace to operate in. Defaults to the team's default workspace. | Example: ```bash curl -s -X POST "https://viraloop.io/api/v1/content/hook-demo" \ -H "Authorization: Bearer $VIRALOOP_API_KEY" \ -H "Content-Type: application/json" \ -d '{"hookVideoUrl":"https://cdn.viraloop.io/assets/hook.mp4","demoVideoUrl":"https://cdn.viraloop.io/assets/product-demo.mp4","prompt":"scheduling a week of posts in one sitting"}' ``` Response: ```json { "success": true, "data": { "id": "665f1b2a9c31a2b3c4d5e757", "format": "Video Hook & Demo", "status": "ready", "title": "i scheduled a whole week in one sitting" } } ``` ### Get a studio render's status `GET /content/{id}` Polls a piece created by the /content operations or accepted from a generation. status is processing, ready or failed. Video formats carry videoUrl when ready; DECK formats (slideshow, wall of text, green screen, ...) have no flat media until rendered - publishing renders them automatically, or GET /content/{id}/download renders on demand and returns the files. A failed render has already refunded its credits. - Scopes: `generations:read` - Credits: undefined - Terminal states: `ready`, `failed` - CLI: `viraloop content get ` - MCP tool: `viraloop_get_content` Query parameters: | Name | Type | Required | Description | | --- | --- | --- | --- | | `workspaceId` | string | no | Workspace to operate in. Defaults to the team's default workspace. | Example: ```bash curl -s "https://viraloop.io/api/v1/content/" \ -H "Authorization: Bearer $VIRALOOP_API_KEY" ``` Response: ```json { "success": true, "data": { "id": "665f1b2a9c31a2b3c4d5e750", "format": "Character Swap", "status": "ready", "videoUrl": "https://cdn.viraloop.io/renders/character_swap.mp4", "thumbnailUrl": "https://cdn.viraloop.io/renders/character_swap.jpg", "durationSec": 8 } } ``` ### Get a content piece's media files `GET /content/{id}/download` Returns the finished media files (mp4 for video formats, the slide images for carousels) for a content piece. For deck formats (slideshow, wall of text, green screen and the other studio decks) the FIRST call starts the server-side render and returns 409; poll this endpoint until it returns 200 with the files (typically under a minute). Generations must be accepted first (POST /generations/{id}/accept) to get a content id. Fetching files here marks the piece accepted for metered (enterprise) billing, exactly like publishing it does. - Scopes: `generations:read` - Credits: none - CLI: `viraloop content download ` - MCP tool: `viraloop_download_content` Query parameters: | Name | Type | Required | Description | | --- | --- | --- | --- | | `workspaceId` | string | no | Workspace to operate in. Defaults to the team's default workspace. | Example: ```bash curl -s "https://viraloop.io/api/v1/content//download" \ -H "Authorization: Bearer $VIRALOOP_API_KEY" ``` Response: ```json { "success": true, "data": { "id": "665f1b2a9c31a2b3c4d5e757", "format": "Slideshow", "files": [ { "type": "image", "url": "https://cdn.viraloop.io/renders/slide-1.jpg" }, { "type": "image", "url": "https://cdn.viraloop.io/renders/slide-2.jpg" } ] } } ``` --- # Getting started The Viraloop API generates ready-to-post short-form videos (wall of text, slideshow, green screen) from your brand's context and publishes them to connected TikTok, Instagram and YouTube accounts. This guide takes you from an empty terminal to a published post. Base URL: `https://viraloop.io/api/v1`. Every response uses one envelope: `{ "success": true, "data": ..., "pagination"? }` on success, `{ "success": false, "error": { "type", "message" } }` on failure. ## 1. Create an API key Create a key in the dashboard at https://viraloop.io/settings/developers. Pick the scopes the key needs; for this guide use `accounts:read`, `generations:write`, `posts:read` and `posts:write`. Keys look like `vl_live_...` and the full key is shown once at creation, so store it somewhere safe (an environment variable or secret manager). ```bash export VIRALOOP_API_KEY=vl_live_... ``` ## 2. Verify auth with /me `GET /me` works with any valid key and returns your team, credit balance, plan, default workspace and the scopes granted to the key. ```bash curl -s https://viraloop.io/api/v1/me \ -H "Authorization: Bearer $VIRALOOP_API_KEY" ``` Or with the CLI: ```bash npm install -g viraloop viraloop login # or rely on the VIRALOOP_API_KEY env var viraloop whoami --json ``` ## 3. Find your connected accounts Posting requires at least one connected social account. Connecting accounts (OAuth) happens in the web app at https://viraloop.io/accounts, not through the API. List what is connected and note the account ids: ```bash curl -s https://viraloop.io/api/v1/accounts \ -H "Authorization: Bearer $VIRALOOP_API_KEY" ``` ```bash viraloop accounts list --json ``` ## 4. Generate content `POST /generations` is synchronous: the request returns when generation finishes, typically 5 to 60 seconds depending on count. It costs no credits in v1. ```bash curl -s -X POST https://viraloop.io/api/v1/generations \ -H "Authorization: Bearer $VIRALOOP_API_KEY" \ -H "Content-Type: application/json" \ -d '{"count": 2}' ``` ```bash viraloop generate --count 2 --json ``` Each suggestion in the response has an `id`, a `caption` (the on-screen text), a `postCaption` (the post body), `hashtags` and a `why` rationale. See the [generations guide](/developers/docs/generations) for the details. ## 5. Publish a post Publish a suggestion you like with `POST /posts`, targeting account ids from step 3: ```bash curl -s -X POST https://viraloop.io/api/v1/posts \ -H "Authorization: Bearer $VIRALOOP_API_KEY" \ -H "Content-Type: application/json" \ -d '{"suggestionId": "", "selectedAccounts": {"tiktok": [""]}, "schedule": "asap"}' ``` ```bash viraloop posts create --suggestion \ --accounts tiktok: --when asap --wait --json ``` The API responds with `202` and a `statusUrl`; the video renders server-side after the call returns. The CLI's `--wait` flag polls for you. ## 6. Poll until it is posted ```bash curl -s https://viraloop.io/api/v1/posts/ \ -H "Authorization: Bearer $VIRALOOP_API_KEY" ``` ```bash viraloop posts get --json ``` Terminal statuses are `posted`, `partial`, `completed` and `failed`; a `renderStatus` of `failed` is also terminal. Once posted, the response includes a per-platform `postUrl` and an analytics snapshot (views, likes, comments, shares). ## Next steps - [Authentication and scopes](/developers/docs/authentication) - [Posts and scheduling](/developers/docs/posts-and-scheduling) - [CLI](/developers/docs/cli) and [MCP](/developers/docs/mcp) - Full endpoint reference: [/developers/reference](/developers/reference) or https://viraloop.io/llms-full.txt for agents --- # Authentication Every request to `https://viraloop.io/api/v1` is authenticated with an API key. Keys are created in the dashboard at https://viraloop.io/settings/developers and look like `vl_live_...`. ## Sending the key Two headers are accepted; use whichever fits your HTTP client: ```bash curl -s https://viraloop.io/api/v1/me \ -H "Authorization: Bearer $VIRALOOP_API_KEY" ``` ```bash curl -s https://viraloop.io/api/v1/me \ -H "X-API-Key: $VIRALOOP_API_KEY" ``` Keys are accepted in headers only. Never put a key in a URL or query string; URLs end up in logs. A missing or invalid key returns `401` with error type `unauthorized`. ## Scopes Each key carries a set of scopes chosen at creation. Calling an endpoint the key is not scoped for returns `403` with error type `forbidden_scope`. - `credits:read`: read the credit balance and ledger (GET /credits) - `workspaces:read`: list and read workspaces (GET /workspaces, GET /workspaces/{id}) - `workspaces:write`: create workspaces (POST /workspaces) - `accounts:read`: list connected social accounts (GET /accounts) - `generations:read`: list and read content suggestions (GET /generations, GET /generations/{id}) - `generations:write`: generate content suggestions (POST /generations) - `posts:read`: list and read posts and the calendar (GET /posts, GET /posts/{id}, GET /calendar) - `posts:write`: create and cancel posts (POST /posts, DELETE /posts/{id}) `GET /me` works with any valid key regardless of scopes; use it to check which scopes a key has (`data.auth.scopes`). ## Managing keys Everything happens at https://viraloop.io/settings/developers: - Create a key with a name and the scopes it needs - The full key is shown once, at creation; only a prefix (like `vl_live_a1b2c3d4`) is visible afterwards - Revoke a key at any time; revoked keys stop working immediately ## Security notes - Store keys in environment variables or a secret manager, never in source control - Give each integration its own key with the minimum scopes it needs, so one leak has a small blast radius and revocation does not break everything else - If a key leaks, revoke it in the dashboard and create a new one - Keys act on behalf of your team; treat them like passwords --- # Generations Generations are AI-created, ready-to-post content suggestions. Viraloop uses the workspace's brand context (identity, tone of voice, content angles) and its Turbo configuration to produce a batch of suggestions you can review and then publish as posts. ## What a suggestion is Each suggestion contains: - `id`: use it later as `suggestionId` when publishing - `format`: one of `walloftext`, `slideshow`, `greenscreen` - `caption`: the on-screen text of the video itself (the hook the viewer reads) - `postCaption`: the post body, the description text published alongside the video on the platform - `hashtags`: hashtags to publish with the post - `why`: a short rationale for why this suggestion should perform - `angle`: the content angle it was derived from - `deck`: the editable render blueprint (text boxes, ratio, media) used to render the video server-side - `status`: `pending` (awaiting review), `accepted` or `scheduled` `caption` and `postCaption` are separate on purpose: the first is part of the video, the second is platform metadata. Both can be overridden when you create the post. ## Formats - `walloftext`: a bold text overlay carried by a simple background video - `slideshow`: a swipeable sequence of image slides with text - `greenscreen`: a green-screen style meme format Pass `format` to force one of them, or omit it to use the workspace's configured content mix. ## Generating `POST /generations` is synchronous: the request returns when generation finishes, typically 5 to 60 seconds depending on `count` (1 to 10). Content generation costs no credits in v1. The endpoint is rate limited to 10 requests per 300 seconds and supports the `Idempotency-Key` header for safe retries. ```bash curl -s -X POST https://viraloop.io/api/v1/generations \ -H "Authorization: Bearer $VIRALOOP_API_KEY" \ -H "Content-Type: application/json" \ -d '{"count": 3, "format": "walloftext"}' ``` ```bash viraloop generate --count 3 --json ``` Optional body fields: `influencerId` features that AI influencer in every suggestion, `workspaceId` targets a workspace other than the team's default. Requires the `generations:write` scope. ## Reviewing List the queue (automation-owned suggestions are excluded) and inspect single suggestions: ```bash viraloop generations list --json viraloop generations get --json ``` `GET /generations` supports `status`, `page`, `limit` and `workspaceId` filters. `GET /generations/{id}` returns the full deck, rationale and remix source. Both require the `generations:read` scope. ## Previewing before accepting `GET /generations/{id}` returns the full `deck`: slide image URLs, caption text boxes (text, position, styling) and the background clip or meme assets for the video formats. Render those client-side (image or clip with the caption text overlaid) as the preview your users accept or skip from. Previewing and accepting cost nothing; billing only happens when a piece's media is downloaded or published. ## Getting the media files (serve them yourself) To fetch a suggestion's finished media instead of (or before) publishing: 1. Accept it: `POST /generations/{id}/accept` returns a `contentId`. 2. Download: `GET /content/{contentId}/download`. The first call starts the server-side render and returns `409`; poll until it returns `200` with the files (typically under a minute). ```bash viraloop generations accept --json viraloop content download --json ``` ## Publishing Publish a suggestion by passing its id to `POST /posts`: ```bash viraloop posts create --suggestion --accounts tiktok: --when asap --wait --json ``` If the suggestion is already scheduled, this returns `409` with error type `conflict`; generate or pick another. See [posts and scheduling](/developers/docs/posts-and-scheduling) for accounts, timing and polling. --- # Posts and scheduling `POST /posts` schedules a post to one or more connected accounts. It needs the `posts:write` scope, is rate limited to 60 requests per 300 seconds, and supports the `Idempotency-Key` header. ## Three content sources The body must provide exactly one content source; they are checked in this order: 1. `suggestionId`: publish a generated suggestion from `POST /generations` (the usual path) 2. `deck` + `format`: publish a raw deck object (advanced; `format` is required when `deck` is provided) 3. `videoUrl` or `images`: publish your own media as-is; URLs must be publicly reachable (`thumbnailUrl` is optional for videos) `caption` and `hashtags` are optional; for suggestion-based posts they default to the suggestion's own values. ## Target accounts `selectedAccounts` is required: an object of platform keys mapping to arrays of account ids from `GET /accounts`, with at least one non-empty platform array. ```json { "selectedAccounts": { "tiktok": ["665f1b2a9c31a2b3c4d5e700"], "youtube": ["665f1b2a9c31a2b3c4d5e711"] } } ``` Connecting accounts (OAuth) happens in the web app, not through the API. Other optional fields: `ownerType` (`brand` or `influencer`; `influencerId` is required when `influencer`), `tiktokSendToInbox` to send a TikTok draft instead of publishing, and `workspaceId`. ## asap vs scheduled - `schedule: "asap"` posts as soon as the render is ready - `schedule: "scheduled"` requires a `scheduledTime` in the future; pass `timezone` as an IANA name (default UTC) ```bash viraloop posts create --suggestion --accounts tiktok: \ --at "2026-07-04T15:00:00Z" --json ``` ## 202 and polling Deck-based posts are rendered server-side after the call returns, so the response is `202` with a `statusUrl`: ```json { "success": true, "data": { "id": "665f1b2a9c31a2b3c4d5e702", "status": "scheduled", "renderStatus": "pending", "statusUrl": "/api/v1/posts/665f1b2a9c31a2b3c4d5e702" } } ``` Poll `GET /posts/{id}` (scope `posts:read`) until a terminal state: - Terminal `status` values: `posted`, `partial`, `completed`, `failed` - A `renderStatus` of `failed` is also terminal The post includes per-platform results: status, `postUrl` and an analytics snapshot (views, likes, comments, shares). With the CLI, `viraloop posts create --wait` does the polling for you; do not use `--wait` for far-future posts, it waits until the post actually publishes. ## Cancelling `DELETE /posts/{id}` cancels a post that has not been published yet (status `scheduled`). Published or in-flight posts cannot be cancelled; that returns `409` with error type `conflict`. ```bash viraloop posts cancel --json ``` ## Calendar `GET /calendar` returns the workspace's posts in a date window; `from` and `to` are required (ISO 8601), `status` is optional. Check what is already queued before scheduling more: ```bash viraloop calendar --from 2026-07-06 --to 2026-07-13 --json ``` ## Idempotency Send an `Idempotency-Key` header (any unique string) on `POST /posts` to make retries safe: a successful response is stored and replayed if you retry with the same key and body, so a network timeout cannot double-post. Reusing a key with a different body returns `409`. See [errors and rate limits](/developers/docs/errors-and-rate-limits). --- # Automations A automation is a batch of AI posts generated at once and auto-scheduled across a posting window. You set a cadence (posts per day, for how many days), Viraloop generates one post per slot using the workspace's brand context, you review the batch, and launching turns every post into a scheduled post at its slot. Automation endpoints need the `automations:read` and `automations:write` scopes. ## Lifecycle `draft` -> `generating` -> `review` -> `active` -> `completed` or `cancelled` - `draft`: created and fully editable, no posts yet; a generation run that produces nothing also falls back here - `generating`: posts are being generated; poll `GET /automations/{id}` until the automation leaves this status - `review`: the batch is ready; inspect the posts, tweak settings or regenerate, then launch - `active`: launched; posts render server-side and publish automatically at their slots - `completed`: the posting window has run its course - `cancelled`: cancelled; unsent posts were removed ## Quota The monthly automation quota depends on the plan. `GET /automations` returns it alongside the list: ```json { "quota": { "monthlyLimit": 8, "usedThisMonth": 2, "remaining": 6 } } ``` Once the quota is used up, starting another automation fails with error type `usage_limit_reached`; see [errors and rate limits](/developers/docs/errors-and-rate-limits). ```bash curl -s "https://viraloop.io/api/v1/automations" \ -H "Authorization: Bearer $VIRALOOP_API_KEY" ``` ```bash viraloop automations list --json ``` ## 1. Create `POST /automations` creates the automation in `draft` (scope `automations:write`, supports the `Idempotency-Key` header). Only `name` is required; everything else can be set now or later via `PATCH`: - `cadence`: `postsPerDay` (1 to 10), `lengthDays` (1 to 90), `startDate` (first posting day, defaults to tomorrow), `timezone` (IANA name) and optional `timeSlots` like `["09:00", "15:00"]` - `selectedAccounts`: platform keys mapping to arrays of account ids from `GET /accounts`, with at least one non-empty platform array. Every post in the automation publishes to all selected accounts. - `settings`: generation settings, all optional with sensible defaults: `contentMix` (format weights, keys `walloftext`, `slideshow`, `greenscreen`, `hookdemo`), `remixRatio`, `angleDistribution`, `mentionBusiness`, `genderPreference`, `ownMediaMix`, `influencerFrequency` - `ownerType` (`brand` or `influencer`; `influencerId` is required when `influencer`), `tiktokMode` (`direct` or `inbox`; inbox sends TikTok drafts to the inbox instead of publishing) and `workspaceId` ```bash curl -s -X POST "https://viraloop.io/api/v1/automations" \ -H "Authorization: Bearer $VIRALOOP_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "July launch week", "cadence": { "postsPerDay": 2, "lengthDays": 7, "timezone": "America/New_York" }, "selectedAccounts": { "tiktok": ["665f1b2a9c31a2b3c4d5e700"] } }' ``` ```bash viraloop automations create --name "July launch week" --posts-per-day 2 --length-days 7 \ --timezone America/New_York --accounts tiktok: --json ``` ## 2. Generate `POST /automations/{id}/generate` generates `postsPerDay x lengthDays` AI posts, each assigned a schedule slot. It is asynchronous: the response is `202` immediately (rate limited to 5 requests per 3600 seconds): ```json { "success": true, "data": { "id": "665f1b2a9c31a2b3c4d5e710", "kind": "automation", "status": "generating", "totalPosts": 14, "generatedCount": 0, "statusUrl": "/api/v1/automations/665f1b2a9c31a2b3c4d5e710" } } ``` Poll `GET /automations/{id}` until the automation leaves `generating`: `review` means the batch is ready; back to `draft` means nothing was generated. Calling generate again while a run is in flight is a no-op that reports progress (`generatedCount` against `totalPosts`). Regenerating a automation in `review` replaces its posts. ```bash curl -s -X POST "https://viraloop.io/api/v1/automations//generate" \ -H "Authorization: Bearer $VIRALOOP_API_KEY" curl -s "https://viraloop.io/api/v1/automations/" \ -H "Authorization: Bearer $VIRALOOP_API_KEY" ``` ```bash viraloop automations generate --json viraloop automations get --json ``` ## 3. Review `GET /automations/{id}/posts` lists the generated posts in schedule order. Each carries `caption`, `postCaption`, `hashtags`, its rationale and its assigned `scheduledTime`: ```bash curl -s "https://viraloop.io/api/v1/automations//posts" \ -H "Authorization: Bearer $VIRALOOP_API_KEY" ``` ```bash viraloop automations posts --json ``` To tweak the automation, `PATCH /automations/{id}` updates `name`, `cadence`, `selectedAccounts`, `settings`, `ownerType`, `influencerId` or `tiktokMode`. Editing is only allowed while the automation is in `draft` or `review`; any other status returns `409` with error type `conflict`. Sub-objects are replaced wholesale, so send the complete `cadence`, `selectedAccounts` or `settings` object, not just the changed keys. ```bash curl -s -X PATCH "https://viraloop.io/api/v1/automations/" \ -H "Authorization: Bearer $VIRALOOP_API_KEY" \ -H "Content-Type: application/json" \ -d '{"selectedAccounts": {"tiktok": ["665f1b2a9c31a2b3c4d5e700"]}}' ``` ```bash viraloop automations update --accounts tiktok: --json ``` If the content itself is off rather than the settings, regenerate instead: it replaces the batch. ## 4. Launch `POST /automations/{id}/launch` converts every generated post into a scheduled post at its slot and activates the automation. It requires status `review` and at least one selected account: ```json { "success": true, "data": { "id": "665f1b2a9c31a2b3c4d5e710", "kind": "automation", "status": "active", "scheduled": 14, "failed": 0, "statusUrl": "/api/v1/automations/665f1b2a9c31a2b3c4d5e710" } } ``` Posts render server-side after the call returns and publish automatically at their slots; nothing more is required. Track them with `GET /posts?automationId=` or the calendar from [posts and scheduling](/developers/docs/posts-and-scheduling). ```bash curl -s -X POST "https://viraloop.io/api/v1/automations//launch" \ -H "Authorization: Bearer $VIRALOOP_API_KEY" ``` ```bash viraloop automations launch --json ``` ## Cancelling `POST /automations/{id}/cancel` cancels the automation and removes its unsent posts. Content that already went out stays live. Cancelling is idempotent: a cancelled or completed automation returns `cancelledPosts: 0`. ```bash curl -s -X POST "https://viraloop.io/api/v1/automations//cancel" \ -H "Authorization: Bearer $VIRALOOP_API_KEY" ``` ```bash viraloop automations cancel --json ``` --- # Influencers and videos AI influencers are virtual personas used to front content: each has a name, a base image and optional persona details like niche, gender and age. A persona can be a photoreal human (`style: "realistic"`, the default) or a stylized non-human character such as a mascot, cartoon or stick figure (`style: "character"`). Feature one in generated suggestions (`influencerId` on `POST /generations`), run influencer-owned automations, or generate talking-head videos of the persona speaking a script. Influencer endpoints need the `influencers:read` and `influencers:write` scopes. ## Listing and getting `GET /influencers` lists the workspace's influencers with `search` (matches name, description and tags), `niche`, `page`, `limit` and `workspaceId` filters. `GET /influencers/{id}` returns one influencer including its base image, preview video state and Turbo settings (`turboEnabled`, `allowedAngles`). ```bash curl -s "https://viraloop.io/api/v1/influencers" \ -H "Authorization: Bearer $VIRALOOP_API_KEY" ``` ```bash viraloop influencers list --json viraloop influencers get --json ``` ## Creating an influencer `POST /influencers` creates an influencer from a base image you provide. `name` and `imageUrl` are required, and `imageUrl` must be a publicly reachable image of the persona. Optional persona fields: `description`, `niche` (one of the app's niche slugs, e.g. `fitness`, `tech`, `food`), `gender` (`male` or `female`), `age` (18 to 99), `ethnicity`, `tags` and `workspaceId`. ```bash curl -s -X POST "https://viraloop.io/api/v1/influencers" \ -H "Authorization: Bearer $VIRALOOP_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name":"Maya","imageUrl":"https://example.com/maya.jpg","niche":"fitness","gender":"female"}' ``` ```bash viraloop influencers create --name Maya --image-url https://example.com/maya.jpg \ --niche fitness --json ``` A short animated preview generates in the background when the team has credits: it costs 10 credits and is skipped when the team is out of credits. The response shows it as `videoPreview` with status `pending`. To generate a base image from a prompt instead of providing one, use the web app. The endpoint is rate limited to 10 requests per 3600 seconds and supports the `Idempotency-Key` header. ### Non-human characters Set `style` to `"character"` to create a persona that is not a real person, and pass `characterDescription` describing how it looks and the art style it is drawn in: ```bash curl -s -X POST "https://viraloop.io/api/v1/influencers" \ -H "Authorization: Bearer $VIRALOOP_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name":"Doodle","imageUrl":"https://example.com/doodle.png","style":"character","characterDescription":"A black stick figure with a round head and large white eyes, clean hand-drawn doodle style on a plain background"}' ``` `style` decides how every prompt the platform builds for this influencer describes it. A `character` influencer is never described as a real person and is never asked for a photograph, across scene images, talking-head videos and automated posting. `gender`, `age` and `ethnicity` do not apply and are ignored; `characterDescription` takes their place in keeping later generations on-model, so keep it specific. Both fields are returned by `GET /influencers` and `GET /influencers/{id}`. `style` is always present (`"realistic"` for anything created before this field existed), and `characterDescription` is `null` for realistic personas. ## Generating a talking-head video `POST /influencers/{id}/videos` generates a talking-head UGC video of the influencer speaking your script (Seedance 2, 9:16). It costs 20 credits, deducted up front; without enough credits it fails with error type `insufficient_credits` (see [credits and limits](/developers/docs/credits-and-limits)). `script` is required (max 1000 characters). Optional fields: `imageUrl` (a reference image, defaults to the influencer's base image), `language` (spoken language, default English), `duration` (5 to 30 seconds) and `mode` (`speaker` for a talking head, `scene` for a wider shot). Rate limited to 10 requests per 600 seconds; supports the `Idempotency-Key` header. ```bash curl -s -X POST "https://viraloop.io/api/v1/influencers//videos" \ -H "Authorization: Bearer $VIRALOOP_API_KEY" \ -H "Content-Type: application/json" \ -d '{"script":"Three things I wish I knew before my first marathon","duration":10}' ``` ```bash viraloop videos create --influencer \ --script "Three things I wish I knew before my first marathon" --duration 10 --json ``` The call is asynchronous and returns `202` with the video in `processing`: ```json { "success": true, "data": { "id": "665f1b2a9c31a2b3c4d5e730", "kind": "video", "influencerId": "665f1b2a9c31a2b3c4d5e720", "status": "processing", "model": "seedance_2", "creditsUsed": 20, "statusUrl": "/api/v1/videos/665f1b2a9c31a2b3c4d5e730" } } ``` ## Polling Poll `GET /videos/{id}` until a terminal status: `completed` (with `videoUrl` set) or `failed` (with `error` set). Generation typically takes 2 to 10 minutes. ```bash curl -s "https://viraloop.io/api/v1/videos/" \ -H "Authorization: Bearer $VIRALOOP_API_KEY" ``` ```bash viraloop videos get --json ``` ## Posting the video A completed video has a publicly reachable `videoUrl`, so publish it like any of your own media via `POST /posts`: ```bash curl -s -X POST "https://viraloop.io/api/v1/posts" \ -H "Authorization: Bearer $VIRALOOP_API_KEY" \ -H "Content-Type: application/json" \ -d '{"videoUrl":"https://cdn.viraloop.io/videos/abc.mp4","selectedAccounts":{"tiktok":["665f1b2a9c31a2b3c4d5e700"]},"schedule":"asap"}' ``` ```bash viraloop posts create --video-url https://cdn.viraloop.io/videos/abc.mp4 \ --accounts tiktok: --when asap --wait --json ``` To publish under an influencer-scoped account, set `ownerType` to `influencer` with the `influencerId` (accounts from `GET /accounts?ownerType=influencer`). See [posts and scheduling](/developers/docs/posts-and-scheduling) for accounts, timing and polling. ## Listing an influencer's videos `GET /influencers/{id}/videos` lists the influencer's generated videos, newest first, with status and URLs. Filters: `status` (`pending`, `processing`, `completed`, `failed`), `page` and `limit`. ```bash curl -s "https://viraloop.io/api/v1/influencers//videos" \ -H "Authorization: Bearer $VIRALOOP_API_KEY" ``` ```bash viraloop videos list --influencer --json ``` ## Assets `GET /assets` (scope `assets:read`) lists the workspace's uploaded media library: the videos and images used as generation backgrounds and sources. The endpoint is read-only in v1; uploading happens in the web app. Filters: `kind` (`video`, `image`, `audio`), `category`, `turboEnabled`, `page`, `limit` and `workspaceId`. ```bash curl -s "https://viraloop.io/api/v1/assets?kind=video" \ -H "Authorization: Bearer $VIRALOOP_API_KEY" ``` ```bash viraloop assets list --json ``` --- # Credits and limits ## Credits `GET /credits` (scope `credits:read`) returns the team's current balance plus a paginated ledger of credit movements: ```bash curl -s https://viraloop.io/api/v1/credits \ -H "Authorization: Bearer $VIRALOOP_API_KEY" ``` ```bash viraloop credits --json ``` Query parameters: `page`, `limit` (max 100) and `type` to filter entries by `recurring`, `topup`, `spending`, `trial`, `spin`, `reward` or `refund`. What costs credits: - `POST /influencers/{id}/videos` (talking-head video): 20 credits per video, deducted up front. - `POST /influencers` (create influencer): 10 credits for the optional animated preview; when the team is out of credits the preview is skipped and the influencer is still created. Content generation (`POST /generations`) and posting (`POST /posts`) consume no credits. If a paid generation fails (provider error or timeout), the deducted credits are refunded automatically and show up in the ledger as a `refund` entry referencing the original spend. Running out of credits returns `402` with error type `insufficient_credits`; top up at https://viraloop.io/settings/billing. Plan quotas (automations, content saves) are separate from credits and return `403` with `usage_limit_reached`. ## Rate limits Limits are applied per API key: - Every endpoint: 300 requests per 60 seconds (default) - `POST /generations`: 10 requests per 300 seconds - `POST /posts`: 60 requests per 300 seconds Exceeding a limit returns `429` with error type `rate_limited` and a `Retry-After` header (seconds). Wait that long, then retry: ```json { "success": false, "error": { "type": "rate_limited", "message": "Too many requests, slow down" } } ``` The generation limit is deliberately tight because each call is synchronous and can produce up to 10 suggestions; batch with `count` instead of looping single-suggestion calls. --- # Errors and rate limits Every error response uses one envelope, whatever the endpoint: ```json { "success": false, "error": { "type": "rate_limited", "message": "Too many requests, slow down", "hint": "Wait for the seconds in the Retry-After header, then retry. Limits are documented at https://viraloop.io/developers/docs/credits-and-limits.md." } } ``` Branch on `error.type`, not on the message text; messages can change, types will not. `hint` is one fixed sentence per type describing the recovery step, meant for agents that have to act without a human reading these docs. It is informational: never parse it, and never branch on it. Paths that match no endpoint return the same envelope with `type: "not_found"` and a 404, so one parser covers the whole API surface. A `401` also carries a `WWW-Authenticate` header pointing at [the protected-resource metadata](https://viraloop.io/.well-known/oauth-protected-resource) (RFC 9728), which lists every scope the API supports. ## Error types - `invalid_input` (400, and 405 for a wrong HTTP method): the request body, query or method is wrong. Fix the request against the [API reference](/developers/reference); the message says which field failed. - `unauthorized` (401): missing, malformed or revoked API key. Check the `Authorization: Bearer vl_live_...` (or `X-API-Key`) header; with the CLI, re-run `viraloop login` or fix `VIRALOOP_API_KEY`. - `insufficient_credits` (402): the team does not have enough credits for a credit-costing operation. The two credit-costing endpoints are `POST /influencers/{id}/videos` (20 credits) and the optional preview in `POST /influencers` (10 credits, skipped instead of failing when out of credits). Top up at https://viraloop.io/settings/billing; do not retry until the balance changes. Failed generations refund their credits automatically. - `forbidden_scope` (403): the key is valid but lacks a required scope. Create a key with the needed scopes at https://viraloop.io/settings/developers; `GET /me` shows the scopes a key has. - `not_found` (404): the resource id does not exist or belongs to another team. Also returned for malformed object ids. - `conflict` (409): the request clashes with current state. The three common cases: cancelling a post that is no longer `scheduled`, publishing a suggestion that is already scheduled, and reusing an `Idempotency-Key` with a different request body. Do not blind-retry; re-read the resource state first. - `rate_limited` (429): too many requests for this key. Wait for the number of seconds in the `Retry-After` header, then retry. Limits: 300 per 60s default, 10 per 300s on `POST /generations`, 60 per 300s on `POST /posts` (see [credits and limits](/developers/docs/credits-and-limits)). - `internal_error` (500 and other 5xx): something failed on our side. Retry with exponential backoff; if it persists, contact support with the request path and time. ## HTTP status to type mapping - 400: `invalid_input` - 401: `unauthorized` - 402: `insufficient_credits` - 403: `forbidden_scope` - 404: `not_found` - 405: `invalid_input` (method not allowed on this endpoint) - 409: `conflict` - 429: `rate_limited` - 5xx: `internal_error` ## Idempotency and the 409 conflict Mutating operations marked idempotent in the API reference (including `POST /generations`, `POST /posts`, `POST /influencers` and `POST /influencers/{id}/videos`) accept an `Idempotency-Key` header (any unique string, for example a UUID). Successful responses are stored for 24 hours; retrying with the same key and the same body replays the stored response instead of re-running the operation, which makes timeouts safe to retry and prevents double credit charges. The CLI generates a key automatically for every mutating command; MCP tools accept an `idempotencyKey` argument with the same semantics. Reusing a key with a different body is treated as a bug in your client and returns `409`: ```json { "success": false, "error": { "type": "conflict", "message": "This Idempotency-Key was already used with a different request body" } } ``` Generate a fresh key per logical operation, and reuse it only for retries of exactly that operation. --- # CLI The `viraloop` CLI wraps the whole API in commands that work for humans and for scripts and agents. ## Install ```bash npm install -g viraloop viraloop --version ``` The command is `viraloop` (alias `vl`). Without installing, use `npx -y viraloop `. ## Authenticate Two options: - `viraloop login`: paste an API key from https://viraloop.io/settings/developers and it is stored for future commands - `VIRALOOP_API_KEY` environment variable: nothing to store; ideal for CI and agents Verify with `viraloop whoami`. ## Commands - `viraloop whoami`: introspect the API key (GET /me) - `viraloop credits`: credit balance and ledger (GET /credits) - `viraloop workspaces list`: list workspaces (GET /workspaces) - `viraloop workspaces get `: get a workspace (GET /workspaces/{id}) - `viraloop workspaces create [--website ]`: create a workspace; the brand profile builds in the background (POST /workspaces) - `viraloop accounts list`: list connected social accounts (GET /accounts) - `viraloop generate`: generate AI post suggestions (POST /generations) - `viraloop generations list`: list suggestions (GET /generations) - `viraloop generations get `: get a suggestion (GET /generations/{id}) - `viraloop generations accept `: accept a suggestion into the library, returns contentId (POST /generations/{id}/accept) - `viraloop posts create`: create and schedule a post (POST /posts) - `viraloop posts list`: list posts (GET /posts) - `viraloop posts get `: get a post with per-platform results (GET /posts/{id}) - `viraloop posts cancel `: cancel a scheduled post (DELETE /posts/{id}) - `viraloop calendar`: posting calendar (GET /calendar) - `viraloop content download `: fetch a content piece's finished media files (GET /content/{id}/download) Commands default to the team's default workspace; pass `--workspace ` to target another. ## Scripting and agents: --json Always pass `--json` when running from a script or agent, and parse the `success` field. The output is the raw API envelope, so the [API reference](/developers/reference) documents every field. ```bash viraloop generate --count 3 --json viraloop posts create --suggestion --accounts tiktok:,youtube: \ --when asap --wait --json ``` `--accounts` takes comma-separated `platform:id` pairs (ids from `viraloop accounts list`). Use `--when asap` or `--at "2026-07-04T15:00:00Z"` (with optional `--timezone` as an IANA name) for timing, `--video-url` or repeatable `--image-url` plus `--caption` for your own media. ## Exit codes - `0`: success - `2`: authentication failed (`unauthorized`); re-run `viraloop login` or fix `VIRALOOP_API_KEY` - `3`: `insufficient_credits`; top up at https://viraloop.io/settings/billing - `4`: `rate_limited`; wait and retry (respect Retry-After) - `1`: any other error ## --wait `viraloop posts create --wait` polls the created post until a terminal state (`posted`, `partial`, `completed`, `failed`; a `renderStatus` of `failed` is also terminal) and prints the final post including per-platform `postUrl`s. Without `--wait`, poll `viraloop posts get --json` yourself. Do not use `--wait` for far-future scheduled posts: it waits until the post actually publishes. --- # MCP Viraloop ships a Model Context Protocol (MCP) server so agent runtimes (Claude Code, Claude Desktop and any other MCP client) can call the API as tools. There are two ways to connect: the remote server over streamable HTTP, or a local stdio server bundled in the CLI. Both need an API key from https://viraloop.io/settings/developers. ## Remote server (nothing to install) The remote server lives at `https://viraloop.io/api/v1/mcp` and speaks the streamable HTTP transport. Authenticate with the same `Authorization` header as the REST API: ```bash claude mcp add --transport http viraloop https://viraloop.io/api/v1/mcp \ --header "Authorization: Bearer vl_live_..." ``` Any MCP client that supports streamable HTTP works the same way: point it at the URL and send `Authorization: Bearer vl_live_...` with every request. ## Local server (stdio via the CLI) The `viraloop` npm package includes a stdio MCP server, started with `viraloop mcp`. It reads the key from the `VIRALOOP_API_KEY` environment variable: ```bash claude mcp add viraloop --env VIRALOOP_API_KEY=vl_live_... -- npx -y viraloop mcp ``` Use the remote server when you want zero install and always-current tools; use the local one when your client only speaks stdio or your network policy prefers a locally spawned process. ## Tools Each tool wraps one API operation and respects the key's scopes (a missing scope surfaces as a `forbidden_scope` error): - `viraloop_get_me`: introspect the API key (GET /me) - `viraloop_get_credits`: credit balance and ledger (GET /credits) - `viraloop_list_workspaces`: list workspaces (GET /workspaces) - `viraloop_list_accounts`: list connected social accounts (GET /accounts) - `viraloop_generate_content`: generate AI post suggestions (POST /generations) - `viraloop_list_generations`: list suggestions (GET /generations) - `viraloop_get_generation`: get a suggestion (GET /generations/{id}) - `viraloop_create_post`: create and schedule a post (POST /posts) - `viraloop_list_posts`: list posts (GET /posts) - `viraloop_get_post`: get a post with per-platform results (GET /posts/{id}) - `viraloop_cancel_post`: cancel a scheduled post (DELETE /posts/{id}) Tool results use the same envelope and error types as the REST API; see [errors and rate limits](/developers/docs/errors-and-rate-limits). Rate limits also apply per key, so a busy agent should back off on `rate_limited`. ## Related - Prefer commands over tools? The [CLI](/developers/docs/cli) covers the same surface. - A packaged Claude Skill with ready-made workflows lives at https://github.com/Viraloop/viraloop-skill. - The full machine-readable surface is at https://viraloop.io/llms-full.txt and https://viraloop.io/openapi/v1.json.