{"skill":{"slug":"tiktok-video-maker","displayName":"TikTok Video Maker","summary":"Generate TikTok-style talking videos from a script and image using the LovelyBots API. Queue a video, poll for completion, and retrieve a download URL — all...","description":"---\nname: tiktok-video-maker\ndescription: Generate TikTok-style talking videos from a script and image using the LovelyBots API. Queue a video, poll for completion, and retrieve a download URL — all in one workflow. Built for marketing teams, ecommerce brands, and agent pipelines that need consistent video output at scale.\nmetadata: { \"openclaw\": { \"requires\": { \"env\": [\"LOVELYBOTS_API_KEY\"], \"bins\": [\"curl\", \"python3\"] }, \"primaryEnv\": \"LOVELYBOTS_API_KEY\", \"emoji\": \"🎬\", \"homepage\": \"https://lovelybots.com/openclaw\" } }\n---\n\n# TikTok Video Maker\n\nGenerate talking videos programmatically using the LovelyBots API. This skill lets you queue a video from a script and source image, poll until it's ready, and return the final video URL.\n\nGet your API key at: https://lovelybots.com/developer\nAPI base URL for bots: https://api.lovelybots.com/api\n\n---\n\n## What This Skill Does\n\n- Submits a video generation job (script + source image → queued video)\n- Polls the job status until completed (or failed)\n- Returns the final video URL\n- Reports credits remaining after each request\n- Accepts image as file upload, URL, or base64 (single `image` field)\n\n---\n\n## Setup\n\n1. Create a LovelyBots account at https://lovelybots.com\n2. Activate a subscription plan (required for API video generation)\n3. Create an API token at https://lovelybots.com/developer\n\nSet your LovelyBots API key as an environment variable:\n\n```bash\nexport LOVELYBOTS_API_KEY=your_api_key_here\n```\n\nOr add it to your openclaw.json:\n\n```json\n{\n  \"skills\": {\n    \"entries\": {\n      \"tiktok-video-maker\": {\n        \"env\": {\n          \"LOVELYBOTS_API_KEY\": \"your_api_key_here\"\n        }\n      }\n    }\n  }\n}\n```\n\n---\n\n## Critical Tips for Agents\n\n1. Use the API host for bot calls: `https://api.lovelybots.com/api`. Do not use the web app host for API requests.\n2. Always send `Authorization: Bearer $LOVELYBOTS_API_KEY` on every API request.\n3. Treat `video.id` and `voice_id` as UUID strings. Never assume numeric IDs.\n4. Poll `GET /api/videos/:id` until terminal status (`completed` or `failed`) with timeout and retry guards.\n5. If using image URLs, they must be public `http/https` URLs. Localhost/private-network URLs are blocked.\n6. Keep auth/user flows on `https://lovelybots.com` (dashboard/login/docs), and keep automation calls on `api.lovelybots.com`.\n7. On non-2xx API responses, surface the error and stop retrying blindly.\n\n---\n\n## Example Prompts\n\n- \"Generate a 30-second product ad video using my image at https://example.com/image.jpg with this script: Welcome to our summer sale...\"\n- \"Make a video with the TikTok Video Maker — use image [url-or-base64-or-upload] and script: [text]\"\n- \"Queue a video generation job and give me the download link when it's done\"\n- \"Create a talking video for my TikTok ad using LovelyBots\"\n\n---\n\n## How to Generate a Video\n\n### Step 1 — Submit the job\n\nBest quality input recommendation:\n- Use a clear, front-facing portrait image.\n- Use `9:16` orientation (for example `1080x1920`).\n\n```bash\ncurl -X POST https://api.lovelybots.com/api/create \\\n  -H \"Authorization: Bearer $LOVELYBOTS_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"script\": \"Welcome to our summer sale. Use code SAVE20 for 20% off everything this week only.\",\n    \"image\": \"https://example.com/your-image-1080x1920.jpg\",\n    \"public\": false,\n    \"action_prompt\": \"Subject smiles warmly and waves at the camera.\",\n    \"camera_prompt\": \"Medium closeup, static camera, cinematic lighting, 4k.\"\n  }'\n```\n\nResponse:\n\n```json\n{\n  \"id\": \"b6f9a32d-3c53-4a6c-9d8c-2f0f7a1b4cde\",\n  \"status\": \"queued\",\n  \"credits_remaining\": 1,\n  \"share_url\": \"https://lovelybots.com/videos/b6f9a32d-3c53-4a6c-9d8c-2f0f7a1b4cde/share/abc123token\"\n}\n```\n\n---\n\n### Step 2 — Poll for completion\n\n```bash\ncurl \"https://api.lovelybots.com/api/videos/$VIDEO_ID\" \\\n  -H \"Authorization: Bearer $LOVELYBOTS_API_KEY\"\n```\n\nResponse when processing:\n\n```json\n{\n  \"id\": \"b6f9a32d-3c53-4a6c-9d8c-2f0f7a1b4cde\",\n  \"status\": \"processing\",\n  \"credits_remaining\": 9,\n  \"share_url\": \"https://lovelybots.com/videos/b6f9a32d-3c53-4a6c-9d8c-2f0f7a1b4cde/share/abc123token\"\n}\n```\n\n### Agent Status Update (recommended)\n\nWhen `status` is `queued` or `processing`, immediately report progress to the user (including `share_url`) before continuing to poll.\n\nTemplate:\n\n```text\nStatus Update:\nJob ID: <id>\nStatus: <status>\nCredits Remaining: <credits_remaining>\nShare URL: <share_url>\n```\n\nResponse when complete:\n\n```json\n{\n  \"id\": \"b6f9a32d-3c53-4a6c-9d8c-2f0f7a1b4cde\",\n  \"status\": \"completed\",\n  \"video_url\": \"https://lovelybots.com/videos/b6f9a32d-3c53-4a6c-9d8c-2f0f7a1b4cde.mp4\",\n  \"share_url\": \"https://lovelybots.com/videos/b6f9a32d-3c53-4a6c-9d8c-2f0f7a1b4cde/share/abc123token\",\n  \"credits_remaining\": 9\n}\n```\n\nResponse if failed:\n\n```json\n{\n  \"id\": \"b6f9a32d-3c53-4a6c-9d8c-2f0f7a1b4cde\",\n  \"status\": \"failed\",\n  \"error\": \"Image could not be processed\",\n  \"credits_remaining\": 10,\n  \"share_url\": \"https://lovelybots.com/videos/b6f9a32d-3c53-4a6c-9d8c-2f0f7a1b4cde/share/abc123token\"\n}\n```\n\n---\n\n### Step 3 — Return the video URL to the user\n\nOnce status is `completed`, return `video_url` to the user. The video is ready to download or share.\n\n---\n\n## Polling Strategy\n\nPoll every 5–10 seconds. Most videos complete within 60–120 seconds. If status is still `processing` after 5 minutes, surface an error to the user.\n\nSuggested polling loop (bash):\n\n```bash\nVIDEO_ID=\"b6f9a32d-3c53-4a6c-9d8c-2f0f7a1b4cde\"\nPOLL_INTERVAL_SECONDS=8\nMAX_WAIT_SECONDS=300\nSTART_TS=$(date +%s)\nHEADERS_FILE=$(mktemp)\ntrap 'rm -f \"$HEADERS_FILE\"' EXIT\n\nextract_json_field() {\n  local key=\"$1\"\n\n  if command -v jq >/dev/null 2>&1; then\n    jq -r --arg key \"$key\" '.[$key] // empty'\n    return\n  fi\n\n  if command -v python3 >/dev/null 2>&1; then\n    python3 -c 'import json,sys; key=sys.argv[1]; data=json.load(sys.stdin); value=data.get(key, \"\"); print(\"\" if value is None else value)' \"$key\"\n    return\n  fi\n\n  echo \"Install jq or python3 to parse API responses in this polling loop.\" >&2\n  return 127\n}\n\nwhile true; do\n  NOW_TS=$(date +%s)\n  if [ $((NOW_TS - START_TS)) -ge \"$MAX_WAIT_SECONDS\" ]; then\n    echo \"Timed out after ${MAX_WAIT_SECONDS}s waiting for video completion.\" >&2\n    break\n  fi\n\n  HTTP_RESPONSE=$(curl -sS --connect-timeout 10 --max-time 30 \\\n    -D \"$HEADERS_FILE\" \\\n    -w $'\\n%{http_code}' \"https://api.lovelybots.com/api/videos/$VIDEO_ID\" \\\n    -H \"Authorization: Bearer $LOVELYBOTS_API_KEY\")\n  CURL_EXIT=$?\n  if [ \"$CURL_EXIT\" -ne 0 ]; then\n    echo \"Polling request failed (curl exit $CURL_EXIT). Retrying...\" >&2\n    sleep \"$POLL_INTERVAL_SECONDS\"\n    continue\n  fi\n\n  HTTP_CODE=$(printf '%s\\n' \"$HTTP_RESPONSE\" | tail -n 1)\n  RESPONSE=$(printf '%s\\n' \"$HTTP_RESPONSE\" | sed '$d')\n\n  if [ \"$HTTP_CODE\" = \"429\" ] || [ \"$HTTP_CODE\" -ge 500 ]; then\n    RETRY_AFTER=$(awk 'tolower($1)==\"retry-after:\" {print $2}' \"$HEADERS_FILE\" | tr -d '\\r' | tail -n 1)\n    if printf '%s' \"$RETRY_AFTER\" | grep -Eq '^[0-9]+$'; then\n      WAIT_SECONDS=\"$RETRY_AFTER\"\n    else\n      WAIT_SECONDS=\"$POLL_INTERVAL_SECONDS\"\n    fi\n    echo \"Transient HTTP $HTTP_CODE while polling. Retrying in ${WAIT_SECONDS}s...\" >&2\n    sleep \"$WAIT_SECONDS\"\n    continue\n  fi\n\n  if printf '%s' \"$HTTP_CODE\" | grep -Eq '^[0-9]{3}$' && [ \"$HTTP_CODE\" -ge 400 ]; then\n    API_ERROR=$(printf '%s\\n' \"$RESPONSE\" | extract_json_field error 2>/dev/null || true)\n    echo \"Polling failed with HTTP $HTTP_CODE${API_ERROR:+: $API_ERROR}\" >&2\n    break\n  fi\n\n  STATUS=$(printf '%s\\n' \"$RESPONSE\" | extract_json_field status) || break\n  if [ -z \"$STATUS\" ]; then\n    API_ERROR=$(printf '%s\\n' \"$RESPONSE\" | extract_json_field error 2>/dev/null || true)\n    if [ -n \"$API_ERROR\" ]; then\n      echo \"API error: $API_ERROR\" >&2\n    else\n      echo \"Unexpected API response (missing status).\" >&2\n    fi\n    break\n  fi\n\n  if [ \"$STATUS\" = \"completed\" ]; then\n    printf '%s\\n' \"$RESPONSE\" | extract_json_field video_url\n    break\n  elif [ \"$STATUS\" = \"failed\" ]; then\n    API_ERROR=$(printf '%s\\n' \"$RESPONSE\" | extract_json_field error 2>/dev/null || true)\n    echo \"Video generation failed${API_ERROR:+: $API_ERROR}\" >&2\n    break\n  fi\n  sleep \"$POLL_INTERVAL_SECONDS\"\ndone\n```\n\n---\n\n## Key Differentiators\n\n- **Consistent identity output** — stable presenter look across generated videos\n- **Failed renders are refunded** — you only pay for successful videos\n- **Editable after generation** — not locked output like HeyGen/Synthesia\n- **No credit burns on retries** — reliable for automated pipelines\n\n---\n\n## API Reference\n\n| Method | Path | Description |\n|--------|------|-------------|\n| POST | /api/create | Submit a video generation job |\n| POST | /api/videos | Alias for /api/create |\n| GET | /api/videos/:id | Get job status and video URL |\n| GET | /api/voices | List available voices (filter by gender/age) |\n\n### POST /api/create — Request Body\n\n| Field | Type | Required | Description |\n|-------|------|----------|-------------|\n| script | string | ✓ | The spoken text for the video |\n| image | file or string | ✓ | Unified image input. Can be multipart file upload, http/https URL, or base64/data URL |\n| public | boolean |  | Whether the video appears in the public feed (default: true) |\n| gender | string |  | `male` or `female` — skips AI detection, speeds up response |\n| age | string |  | `young_adult`, `adult`, `mature`, `middle_aged`, or `older` |\n| action_prompt | string |  | Optional action/performance guidance |\n| camera_prompt | string |  | Optional camera/framing guidance |\n| voice_id | string (UUID) |  | Specific voice ID from `GET /api/voices` — skips AI detection and auto-selection entirely |\n\nImage guidance:\n- Use front-facing portrait images for best lip-sync stability.\n- Use `9:16` orientation (recommended `1080x1920`).\n- If `image` is a URL, it must be publicly reachable (`localhost` and private-network hosts are blocked).\n\n### GET /api/voices — Response\n\n| Field | Type | Description |\n|-------|------|-------------|\n| voices[].id | string (UUID) | Use as `voice_id` in create requests |\n| voices[].name | string | Voice display name |\n| voices[].gender | string | `male` or `female` |\n| voices[].age | string | `young_adult`, `adult`, `mature`, `middle_aged`, or `older` |\n\n### GET /api/videos/:id — Response\n\n| Field | Type | Description |\n|-------|------|-------------|\n| id | string (UUID) | Job ID |\n| status | string | queued / processing / completed / failed |\n| video_url | string | Download URL (only when completed) |\n| share_url | string | Public shareable page URL (always present) |\n| credits_remaining | integer | Videos remaining in your plan this month |\n| error | string | Error message (only when failed) |\n\n---\n\n## Troubleshooting\n\n| Problem | Likely Cause | Fix |\n|---|---|---|\n| `401 Invalid or expired API token` | Missing/incorrect Bearer token | Regenerate token at `https://lovelybots.com/developer` and send `Authorization: Bearer $LOVELYBOTS_API_KEY` |\n| `403` errors (`Please select a plan`, monthly limit, or IP not allowed) | Plan/usage/IP restrictions | Ensure subscription is active, check monthly limit, and verify token IP allowlist settings |\n| `404 Video not found` | Wrong `video.id` or token user mismatch | Use the UUID returned by create response, and poll using the same API token owner |\n| `422 image is required` or image URL errors | Missing image or blocked URL | Send `image` as upload/URL/base64. If URL, it must be public `http/https` (no localhost/private network) |\n| `429 Rate limit exceeded` | Too many requests | Increase polling interval, add jitter/backoff, retry later |\n| `cf-mitigated: challenge` header appears | Request sent to proxied host | Use `https://api.lovelybots.com/api/...` (DNS-only API host), not `https://lovelybots.com/api/...` |\n| Poll loop times out | Long render time or transient API/network issue | Raise `MAX_WAIT_SECONDS`, inspect API error payload, and retry with same `video.id` |\n\n---\n\n## Links\n\n- Get API key: https://lovelybots.com/developer\n- API base URL: https://api.lovelybots.com/api\n- Public feed: https://lovelybots.com/feed\n- Full docs: https://lovelybots.com/openclaw\n- Homepage: https://lovelybots.com\n","topics":["Marketing"],"tags":{"latest":"1.0.3"},"stats":{"comments":0,"downloads":486,"installsAllTime":18,"installsCurrent":0,"stars":1,"versions":4},"createdAt":1774435402580,"updatedAt":1778492183196},"latestVersion":{"version":"1.0.3","createdAt":1774843601893,"changelog":"- Added a recommended agent status update step when polling for job completion.\n- Encourages reporting progress (including share URL) to the user while the video is queued or processing.\n- Includes a message template for agent status updates in the documentation.\n- No code or interface changes; documentation update only.","license":"MIT-0"},"metadata":{"setup":[{"key":"LOVELYBOTS_API_KEY","required":true}],"os":null,"systems":null},"owner":{"handle":"georgegally","userId":"s17fw6yh5t7367n0aff70byw2h83kxpp","displayName":"George Gally","image":"https://avatars.githubusercontent.com/u/6388005?v=4"},"moderation":null}