# GEN — Auto Content Engine API (Full Reference) > Exhaustive endpoint reference. This doc is organized around the **5-step journey**, not by resource, so every endpoint sits next to the endpoints it gets called alongside in real workflows. For a teaching-style introduction with runnable curl chains, read `https://api.gen.pro/llms.txt` first. This file is the full spec. ## Base URLs and Auth - `https://api.gen.pro/v1` — agent setup, vidsheets, generations, renders, content monitoring, publishing - `https://agent.gen.pro/v1` — content ideas, research, conversations, chat Auth on both surfaces: - `X-API-Key: ` — recommended; create via `POST /v1/persisted_tokens` or in the GEN dashboard - `Authorization: Bearer ` — for browser-login apps All `/autocontentengine/*` endpoints require `agent_id` as a query parameter and an active GEN credit balance on the agent's workspace. ## OpenAPI Full OpenAPI 3.1 spec: https://api.gen.pro/openapi.yaml. Every operation carries an `x-phase` extension set to one of: `setup`, `ideas`, `convert`, `edit`, `export`. --- # Phase 1 — Setup Everything you do once per agent: create the agent, configure identity / voice / personality / inspiration sources, set up API keys, manage workspaces. ## Discovery ``` GET /v1/me → { id, email, name, username, created_at } GET /v1/workspaces → [{ id, name }] GET /v1/agents?workspace_id={id} → [{ id, name }] ``` ## Agents ``` POST /v1/agents Body: { organization_id?, agent: { name, description?, time_zone?, eleven_lab_api_key?, hume_ai_api_key?, agent_avatars_attributes? } } → { agent: { id, name, description, organization_id, time_zone, primary_avatar_id, primary_avatar_url, role, default_user_voice } } Errors: 422 agent_creation_failed, 422 organization_not_found, 422 invalid_api_key GET /v1/agents/{id}?with_organization_uuid=true → { id, name, description, organization_id, time_zone, primary_avatar_id, primary_avatar_url, agent_setup_project_id, role, default_user_voice: { id, name, gender, language, provider, url } } Errors: 403 permission_denied, 422 agent_not_found PATCH /v1/agents/{id} Body: { agent: { name?, description?, time_zone?, eleven_lab_api_key?, hume_ai_api_key?, agent_avatars_attributes?, default_user_voice_attributes?: { source, voice_id } } } → updated agent Errors: 403 permission_denied, 404 user_voice_resource_not_found, 422 agent_not_found, 422 validation_error, 422 invalid_api_key, 422 voice_id_required DELETE /v1/agents/{id} → 200 empty Errors: 403 forbidden GET /v1/agents/{agent_id}/avatars?cursor={cursor} → [{ id, is_primary, url, thumbnail_url, degod_avatar_id }] (20/page, cursor pagination) POST /v1/agents/{agent_id}/avatars Body (multipart): agent_avatars_attributes[]: { file (JPEG/PNG/WebP) or degod_avatar_id } → 200 empty Errors: 422 avatar_creation_failed PATCH /v1/agents/{agent_id}/avatars/{id} Marks avatar primary; all others non-primary. → 200 empty Errors: 422 avatar_update_failed DELETE /v1/agents/{agent_id}/avatars/{id} id may be underscore-joined for multi-delete (e.g. "7_8_9"). → 200 empty ``` ## Agent Core (preferred single-call setup) **Use this for all agent setup reads and writes.** Flat endpoint. Every field name mirrors the GEN Setup canvas. ### Three critical distinctions - `linked_accounts` = agent's OWN brand socials (what it posts from) - `monitored` = inspiration sources (creators / hashtags / keywords to watch) - `research_topics` = expertise areas the platform researches daily (labeled "Expertise" in UI) - `description` = 2-3 sentence summary (max 500 chars) - `personality` = full persona text (max 20000 chars) ### GET /v1/agents/{agent_id}/core ```json { "agent_id": "abc123", "brand_name": "Santiago", "description": "San Antonio street food scout who hunts the best tacos.", "identity_type": "character", "goal": "growth", "target_platforms": ["tiktok", "instagram"], "shortform": true, "longform": false, "onboarding_status": "profile_complete", "keywords": ["streetfood", "tacotok", "sanantonioeats"], "monitored": [ { "handle": "https://tiktok.com/@keilapacheco", "item_type": "account" }, { "handle": "tacotok", "item_type": "hashtag" } ], "research_topics": [ { "topic": "new food truck openings in San Antonio" } ], "linked_accounts": [ { "id": 42, "url": "https://tiktok.com/@santiago_real", "platform": "tiktok", "display_name": "Santiago" } ], "personality": "Santiago grew up eating at his tia taco stand in south San Antonio...", "default_user_voice": { "voice_id": "21m00Tcm4TlvDq8ikWAM", "name": "Rachel", "source": "public" } } ``` ### PATCH /v1/agents/{agent_id}/core Flat merge-patch. Send only the fields you want to change. Unknown fields return 422. Field-level semantics: - Scalars replace: `brand_name`, `description`, `identity_type`, `goal`, `shortform`, `longform`, `onboarding_status`, `personality` - List fields use FULL replacement (missing = deleted): `keywords`, `target_platforms`, `monitored`, `research_topics`, `linked_accounts` - `linked_accounts` diffing: rows with `id` are updated in place, rows without `id` are inserted, rows absent from the list are deleted. To add one row without touching others: GET, append, PATCH. - `default_user_voice` replaces Keyword auto-mirror: sending `keywords` without `monitored` auto-populates `monitored` with `[{ handle: kw, item_type: "keyword" }]`. Validation: - `description` max 500 chars - `personality` max 20000 chars - `identity_type` enum: `"brand"` | `"character"` - `monitored[].item_type` enum: `"account"` | `"hashtag"` | `"keyword"` - `research_topics` max 20 items, each `topic` max 500 chars - `linked_accounts[].url` required - All nested objects reject unknown keys Response: `200` on full success. On partial failure, `207 Multi-Status` with per-section results: ```json { "brand": { "status": "ok", "data": { "brand_name": "Santiago", ... } }, "personality": { "status": "ok", "data": "Santiago grew up..." }, "voice": { "status": "error", "error": "voice service unavailable" } } ``` Example: ```bash curl -X PATCH "https://api.gen.pro/v1/agents/abc123/core" \ -H "X-API-Key: $GEN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "brand_name": "Santiago", "description": "San Antonio street food scout who hunts the best tacos.", "identity_type": "character", "goal": "growth", "target_platforms": ["tiktok", "instagram"], "shortform": true, "longform": false, "keywords": ["streetfood", "tacotok", "sanantonioeats"], "monitored": [ { "handle": "https://tiktok.com/@keilapacheco", "item_type": "account" } ], "research_topics": [ { "topic": "new food truck openings in San Antonio" } ], "linked_accounts": [ { "url": "https://tiktok.com/@santiago_real", "platform": "tiktok", "display_name": "Santiago" } ], "personality": "Santiago grew up eating at his tia taco stand...", "default_user_voice": { "voice_id": "21m00Tcm4TlvDq8ikWAM", "source": "public" } }' ``` ## Voice API (Agent Core sub-surface) All under `/v1/agents/{agent_id}/voice/*`. PAT auth. ### Library ``` GET /v1/agents/{agent_id}/voice/library?source={public|user_designed|user_trained|user_elevenlabs} → { "voices": [ { "voice_id": "21m00...", "name": "Rachel", "source": "public", "preview_url": null }, { "voice_id": "huv-1", "name": "Custom", "source": "user_designed", "preview_url": null }, { "voice_id": "huv-2", "name": "Clone 1", "source": "user_trained", "preview_url": null } ], "total": 3 } ``` ### ElevenLabs integration Users connect their own ElevenLabs API key to unlock their personal voice library and use their own quota. ``` GET /v1/agents/{agent_id}/voice/integrations/elevenlabs → { connected, masked_key } POST /v1/agents/{agent_id}/voice/integrations/elevenlabs Body: { api_key } → { connected: true, user: { subscription } } POST /v1/agents/{agent_id}/voice/integrations/elevenlabs/test Body: { api_key } → { valid } (no save) DELETE /v1/agents/{agent_id}/voice/integrations/elevenlabs → { connected: false } ``` ### Voice design — 4-step prompt-based flow Build a voice from a text description. Steps 1-3 are cheap; step 4 persists. ``` 1. POST /v1/agents/{agent_id}/voice/design/generate-script Body: { language? } → { voice_sample: "..." } 2. POST /v1/agents/{agent_id}/voice/design/generate-description Body: { gender (required), voice_description?, language?, script? } → { voice_description: "..." } 3. POST /v1/agents/{agent_id}/voice/design/generate-samples Body: { text (required), description? } → { samples: [{ generation_id, audio }, { generation_id, audio }, { generation_id, audio }] } 4. POST /v1/agents/{agent_id}/voice/design Body: { generation_id (required), name (required), gender?, language?, description? } → created voice resource ``` `generation_id` is opaque. Don't parse or store it beyond step 4. ### Voice cloning (synchronous) ``` POST /v1/agents/{agent_id}/voice/clone Body variant A (preferred): { name (required), audio_url, gender?, language?, description? } Body variant B (inline): { name (required), audio_base64, gender?, language?, description? } ``` Exactly one of `audio_url` / `audio_base64`. Small clips only for base64; prefer audio_url for files. ### Voice delete + preview ``` DELETE /v1/agents/{agent_id}/voice/{voice_id} — delete user-owned voice POST /v1/agents/{agent_id}/voice/{voice_id}/preview Body: { text } — **ASYNC**. → { user_job_id } GET /v1/agents/{agent_id}/voice/preview/{job_id} — poll the preview When status == "completed", audio URL is in output_resources. ``` ## Organizations (Workspaces) ``` GET /v1/organizations → [{ id, uuid, organization_id, name, avatar: { url, thumbnail_url }, user_role, credit, available_credit: { generic, aura }, total_members, credit_plan: { id, name, cycle } }] POST /v1/organizations Body: { organization: { name, avatar? (file) } } → { organization_id } Errors: 422 validation_error GET /v1/organizations/{organization_id} → { id, uuid, organization_id, name, avatar, user_role, credit, available_credit, total_members, credit_plan } Errors: 422 organization_not_found PATCH /v1/organizations/{organization_id} Body: { organization: { name?, avatar? (file) } } Errors: 403 forbidden (not owner/manager), 422 organization_not_found, 422 validation_error DELETE /v1/organizations/{organization_id} Permanent. Owner role required. Irreversible. Errors: 403 forbidden (not owner), 422 organization_not_found ``` ## API Keys ``` POST /v1/persisted_tokens Body: { name?, expires_in? } → { id, name, token_type, status, token } (token shown once) GET /v1/persisted_tokens → [{ id, name, token_type, created_at, expires_at, last_used_at, revoked_at, status }] PATCH /v1/persisted_tokens/{id} Body: { persisted_token: { name } } DELETE /v1/persisted_tokens/{id}/revoke DELETE /v1/persisted_tokens/revoke_all ``` --- # Phase 2 — Ideas Everything related to proposing and refining video ideas backed by real trending data. **Base URL for this phase:** `https://agent.gen.pro/v1` (plus content monitoring on `api.gen.pro`). ## Runs (trigger ideas, refine, chat, set preferences) Same endpoint for every intent — the natural-language `message` determines what the agent does. ``` POST /v1/agent/run Body: { message, agent_id, conversation_id?, // omit to start a new conversation; pass to refine attachments?, // optional URLs for vision grounding context?: { vidsheet_id? }, debug? } → { run_id, conversation_id, status: "running", firebase_path } ``` Message patterns: - Generate ideas: `"Generate 5 content ideas grounded in this weeks trends"` (no conversation_id → new conv) - Refine: pass `conversation_id`, `"Make idea 1 punchier. Drop idea 3."` - Set preference: `"remember: always use statement hooks, never questions"` - Remove preference: `"forget: that rule about statement hooks"` - Simple chat: `"What performed best last week in my niche?"` ``` GET /v1/agent/runs/{run_id} → { run_id, conversation_id, status: "running" | "completed" | "failed", messages: [{ role, content, data }] } POST /v1/agent/runs/{run_id}/approve — approve or reject a pending action. Body: { approved: true|false } ``` Live progress: - Use the `firebase_path` returned by `POST /v1/agent/run` as the run-specific progress path. - Keys: `status`, `plan` (numbered checklist), `thinking`, `messages` ## Conversations ``` GET /v1/agent/conversations?agent_id={id} → { conversations: [{ id, agent_id, title, pinned, created_at, updated_at, run_count }] } GET /v1/agent/conversations/{id} — conversation metadata GET /v1/agent/conversations/{id}/messages — all messages (chronological) GET /v1/agent/conversations/{id}/runs — all runs in the conversation PATCH /v1/agent/conversations/{id} — Body: { title?, pinned? } DELETE /v1/agent/conversations/{id} — deletes conversation, runs, messages ``` ## Content Ideas Ideas produced by runs are first-class records. ``` GET /v1/agent/ideas?agent_id={id}&status={status} → [{ id, idea_id, agent_id, title, hook, full_script, description, video_type, video_type_id, status, estimated_duration, aspect_ratio, data: { selected_assets: [ { url, type, description, usage, source, clip_range: { start, end } } ], project_manifest: { video_title, total_duration, aspect_ratio, global_extracted_assets: [...], timeline_layers: [...] }, inspiration_sources: [ { type, url, creator, watch_count, like_count, what_was_used } ], rationale }, created_at }] POST /v1/agent/ideas/{id}/status — cycle to next status PUT /v1/agent/ideas/{id}/status/{status} — set explicit status ``` Status flow: `generated → approve_to_create → ready_for_review → approved_to_post → posted` Edit/rejection statuses: `change_idea`, `change_video`, `rejected`. Video type IDs: `0=Any, 1=Talking Avatar, 2=Green Screen, 3=Montage, 4=Text-Driven, 5=POV Object, 6=Narrated/VO, 8=Split Screen, 9=Skit`. ## Research (standalone) ``` POST /v1/research Body: { topic, depth: "quick" | "default" | "deep", agent_id } → { run_id, conversation_id, status: "running", firebase_path } ``` Poll `GET /v1/agent/runs/{run_id}` until `status` is `completed`. Completed messages contain the structured research findings and summary. Depths: `quick`, `default`, `deep`. ## Research topics (daily background research) Configured via Agent Core (`research_topics` field). The platform researches each topic daily and feeds findings into the Ideas Engine automatically. No poll needed — just set topics and consume ideas downstream. ## Content Monitoring (trending data into agent knowledge base) **Base URL for this sub-surface:** `https://api.gen.pro/v1` ``` POST /v1/user_jobs?agent_id={id} Content-Type: multipart/form-data Body: - user_job[user_job_type] = "train_social" - user_job[data] = JSON string: platform (required): "tiktok" | "instagram" | "youtube" type (required): "username" | "hashtag" | "keyword" (not all combos supported) value (required): @creator, #topic, or plain text days: 0 | 1 | 7 | 30 | 90 | 180 country: two-letter code (us, gb, etc.) max_results: 1-50 monitoring: false (default, one-time) | true (ongoing) comment_monitoring: true | false → { user_job_id } PUT /v1/user_jobs/{id}?agent_id={id} Flat params: platform, type, value, days, country, max_results, monitoring, comment_monitoring Only pending/processing jobs can be updated. ``` ### Platform capabilities | Platform | Username | Hashtag | Keyword | |----------|----------|---------|---------| | TikTok | Yes | Yes | Yes | | Instagram | Yes | Yes | No | | YouTube | Yes | Yes | Yes | ### Pricing Per video/post: $0.015. Per comment (when enabled): $0.002. ### Reading scraped data No raw data dump endpoint — query through the agent: - **Agent Chat**: `POST /v1/agent/run` with natural-language questions - **Automation Pipelines**: scheduled prompts that populate vidsheet rows with results Status flow: `pending → processing → completed` (one-time) or stays `processing` (monitoring). Poll `GET /v1/user_jobs/{id}?agent_id={id}`. --- # Phase 3 — Convert Everything related to taking an idea and producing an Auto Content Engine (vidsheet) to work it in. ## Templates Templates are pre-configured engines synced from a curated library. Cloning is the 80% path. ``` GET /v1/templates/projects?page={page} → [{ id, slug, title, name, description, cover, tutorial_url, tags: [], recent_generated_contents: [{ id, type, thumbnail_url }] }] (20/page, page starts at 1.) GET /v1/templates/projects/{slug} slug can be a URL slug, UUID, or numeric ID. → full template definition with columns, layer stacks, recent renders POST /v1/templates/spreadsheets/{slug}/clone Body: { agent_id } → full engine object with columns, rows, cells, layer stack ``` Default template columns: TEXT 1, TEXT 2, PROMPT (ingredient), VIDEO, FINAL VIDEO, STATS. Cloning is **free** — no credits consumed. ## Auto Content Engine (create from scratch) If no template matches, build manually. ``` POST /v1/autocontentengine?agent_id={id} Body: { spreadsheet: { title, is_default? } } → full engine with empty columns / rows GET /v1/autocontentengine/{id}?agent_id={id}&with_execution_cost=true → full engine with nested spreadsheet_columns, spreadsheet_rows (each with spreadsheet_cells), global_variables_sheet POST /v1/autocontentengine/{id}/clone?agent_id={id} Body: { target_agent_id? } → new engine, optionally under a different agent ``` --- # Phase 4 — Edit Everything related to filling the vidsheet: cells, layers, creation cards, generations, variables, assets, automation. ## Rows ``` GET /v1/autocontentengine/{id}/rows?agent_id={id} POST /v1/autocontentengine/{id}/rows?agent_id={id} Body: { spreadsheet_row: { position? } } GET /v1/autocontentengine/{id}/rows/{row_id}?agent_id={id} POST /v1/autocontentengine/{id}/rows/{row_id}/duplicate?agent_id={id} PUT /v1/autocontentengine/{id}/rows/update_positions?agent_id={id} Body: { rows: [{ id, position }] } PUT /v1/autocontentengine/{id}/rows/mass_update?agent_id={id} ``` ## Columns Only `ingredient` role columns can be created / updated / deleted by users. System roles (`video`, `final_video`, `stats`, `global_variable_*`) are platform-managed. Types: `text`, `image`, `video`, `audio`. ``` GET /v1/autocontentengine/{id}/columns?agent_id={id} POST /v1/autocontentengine/{id}/columns?agent_id={id} Body: { spreadsheet_column: { title, type, position? } } POST /v1/autocontentengine/{id}/columns/{col_id}/duplicate?agent_id={id} PATCH /v1/autocontentengine/{id}/columns/update_positions?agent_id={id} PUT /v1/autocontentengine/{id}/columns/mass_update?agent_id={id} ``` ## Cells ``` GET /v1/autocontentengine/{id}/cells/{cell_id}?agent_id={id} PATCH /v1/autocontentengine/{id}/cells/{cell_id}?agent_id={id} Body: { spreadsheet_cell: { value } } PUT /v1/autocontentengine/{id}/cells/mass_update?agent_id={id} PATCH /v1/autocontentengine/{id}/cells/{cell_id}/set_default_user_job?agent_id={id} Body: { user_job_id } ``` ## Layers Per-cell timeline elements. `position` is z-index. ``` POST /v1/autocontentengine/{id}/cells/{cell_id}/layers?agent_id={id} Body: { video_layer: { name, type, position? } } GET /v1/autocontentengine/{id}/cells/{cell_id}/layers/{layer_id}?agent_id={id} PATCH /v1/autocontentengine/{id}/cells/{cell_id}/layers/{layer_id}?agent_id={id} DELETE /v1/autocontentengine/{id}/cells/{cell_id}/layers/{layer_id}?agent_id={id} POST .../layers/{layer_id}/duplicate?agent_id={id} PUT .../layers/update_positions?agent_id={id} ``` ## Generations (trigger + poll) ``` POST /v1/autocontentengine/{id}/cells/{cell_id}/generate?agent_id={id} Body: { generation_type, data: { ... } } → { generation_id, status: "pending" } POST /v1/autocontentengine/{id}/cells/{cell_id}/layers/{layer_id}/generate?agent_id={id} Body: { generation_type, data: { ... } } → { generation_id, status: "pending" } GET /v1/generations/{id} → { id, status, user_job_type, result, output_resources: [{ id, url, thumbnail_url, object_type }] } POST /v1/generations/{id}/stop — credits refunded; status → "stopped" POST /v1/generations/{id}/continue — credits re-charged; status → "pending" ``` Status flow: `pending → processing → completed | failed | stopped`. ## Creation Card Types (exact `generation_type` + `data`) ### text ``` generation_type: "text" Models: gemini_2_0_flash, gemini_2_5_pro, gpt_4o, gpt_4o_mini, o3_mini, o4_mini, claude_sonnet_4 data: { prompt, model, variables: { key: value } } Output: text string in cell value ``` ### image_from_text ``` generation_type: "image_from_text" Models: gemini_image, gemini_pro_image, midjourney Aspect ratios: "1:1", "9:16", "16:9", "4:3", "3:4" data: { prompt, model, aspect_ratio, variables } Output: image content resource ``` ### video_from_text ``` generation_type: "video_from_text" Models: veo_3, veo_3_fast, veo_3_1, veo_3_1_fast, sora_2, kling_1_6, seedance_pro, seedance_pro_1_5 Aspect ratios: "1:1", "9:16", "16:9" Durations: 5s, 10s (model-dependent) data: { prompt, negative_prompt?, model, aspect_ratio, duration, variables } Output: video content resource ``` ### video_from_image ``` generation_type: "video_from_image" Models: kling_2_1, kling_2_6, veo_3, veo_3_fast, veo_3_1, veo_3_1_fast, sora_2, seedance_lite, seedance_pro, seedance_pro_1_5 data: { prompt, negative_prompt?, model, image_resource_id, image_tail_resource_id?, aspect_ratio, duration } Output: video content resource ``` ### video_from_ingredients ``` generation_type: "video_from_ingredients" Models: pika, kling_1_6, seedance_lite, veo_3_1, veo_3_1_fast data: { prompt, model, asset_resource_ids: [], aspect_ratio, duration } Output: video content resource ``` ### speech_from_text ``` generation_type: "speech_from_text" Voice methods: my_voices | design_voice | clone_voice data (my_voices): { script, voice_method: "my_voices", voice_id, enhance_voice?, speed?, language? } data (design_voice): { script, voice_method: "design_voice", language, gender, enhance_voice? } data (clone_voice): { script, voice_method: "clone_voice", audio_resource_id } Output: audio content resource ``` ### lipsync ``` generation_type: "lipsync" Models: sync_so, gen data: { model, video_resource_id, audio_resource_id } Output: video content resource with synced lip movements ``` ### captions ``` generation_type: "captions" Models: gemini data: { model, source_resource_id } Output: caption / subtitle data ``` ### media (pass-through upload) ``` generation_type: "media" data: { content_resource_id } Output: attaches an uploaded resource to the cell (no AI) ``` ### render (composite) See Phase 5 — it's the export step. ## Global Variables Template substitution for `{{variable_name}}` across prompts and cells. ``` GET /v1/autocontentengine/{id}/global_variables?agent_id={id} POST /v1/autocontentengine/{id}/import_global_variables?agent_id={id} (multipart XLSX upload) GET /v1/autocontentengine/global_variables_template (XLSX template download) ``` ## Content Resources (upload and manage assets) ``` GET /v1/content_resources?agent_id={id}&type={image|video|audio|zip|safe_tensors}&project_id={id}&ids[]={id}&page={page} → [{ id, url, thumbnail_url, file_name, content_type }] (20/page, page starts at 0) POST /v1/content_resources?agent_id={id} Body (multipart): content_resource[file] (required), asset_folder[id]? → { content_resource: {...}, generator: null } Errors: 403 forbidden, 422 content_resource_creation_failed GET /v1/content_resources/{id}?agent_id={id} → { content_resource: {...}, generator: { id, status, type } | null } generator is null for manual uploads; populated for AI-generated resources. PATCH /v1/content_resources/{id}?agent_id={id} Body: { content_resource: { filename } } DELETE /v1/content_resources/{id}?agent_id={id} ``` ### Asset Library (files + folders, unified view) ``` GET /v1/asset_libraries?agent_id={id}&folder_id={id}&asset_type={image,video,audio,folder}&search={q}&order={recent}&page={p}&page_size={s} → [ { id, type: "AssetFolder", name }, { id, type: "ContentResource", url, thumbnail_url, file_name, content_type } ] ``` ### Asset folders ``` POST /v1/asset_folders?agent_id={id} Body: { asset_folder: { name, parent_id? } } PATCH /v1/asset_folders/{id}?agent_id={id} Body: { asset_folder: { name } } DELETE /v1/asset_folders/{id}?agent_id={id} — deletes folder AND all contents (subfolders + files) ``` ### Direct Upload (large files, up to 1 GB) ``` POST /v1/direct_upload Body: { blob: { filename, byte_size (max 1GB), checksum (base64 MD5), content_type, metadata? } } → { id, key, filename, content_type, byte_size, checksum, signed_id, direct_upload: { url, headers: { Content-Type, Content-MD5 } } } Workflow: 1. POST /v1/direct_upload 2. PUT file to direct_upload.url with the returned headers 3. POST /v1/content_resources?agent_id={id} with signed_id as content_resource[file] Compute checksum: openssl md5 -binary file | base64 ``` ## Automation ``` GET /v1/agents/{agent_id}/automation_config PATCH /v1/agents/{agent_id}/automation_config Body: { automation_config: { webhook_url?, callback_url?, polling_enabled? } } POST /v1/agents/{agent_id}/automation_config/test_webhook POST /v1/agents/{agent_id}/automation_config/test_callback ``` Once configured, generations POST status updates to your `webhook_url`. --- # Phase 5 — Export Render the final composite, poll to completion, download the MP4, optionally publish to TikTok. ## Render the Final Video Render is an async generation — same polling pattern as every other generation. ``` POST /v1/autocontentengine/{id}/cells/{cell_id}/render?agent_id={id} → { generation_id, status: "pending" } ``` The target `cell_id` is the cell in the `final_video` column for a given row. That cell composites all layers on its row's video cell. ### Polling the render ``` GET /v1/generations/{generation_id} ``` Status flow: ``` pending → processing → completed | failed ``` On `completed`: ```json { "id": 56789, "status": "completed", "output_resources": [ { "id": 4821, "url": "https://cdn.gen.pro/outputs/render_xyz.mp4", "thumbnail_url": "https://cdn.gen.pro/thumbnails/render_xyz.jpg", "object_type": "video" } ] } ``` `output_resources[0].url` is a public CDN URL — no auth needed to download. `thumbnail_url` is a JPEG of the first frame for previews. ### Stop / continue ``` POST /v1/generations/{id}/stop — full refund POST /v1/generations/{id}/continue — re-charge, resume ``` ### Render failures On `status: "failed"`, `failed_reason` contains a user-facing message. Credits are fully refunded. Retry by calling `POST /render` again — a new generation_id is returned. ### Listing past renders for an engine (via content resources) All render outputs are content resources on the agent, filterable by type: ``` GET /v1/content_resources?agent_id={id}&type=video ``` ## Download ``` curl -L -o final_video.mp4 "https://cdn.gen.pro/outputs/render_xyz.mp4" ``` CDN URLs are public and permanent until you explicitly delete the content resource. Individual layer outputs are also downloadable via each layer's `default_user_job.output_resources[0].url`. ## Publish to Social Currently TikTok. Instagram and YouTube coming. ``` POST /v1/user_jobs?agent_id={id} Content-Type: application/json Body: { "user_job_type": "publish_content", "data": "{\"platform\":\"tiktok\",\"media_url\":\"https://cdn.gen.pro/outputs/render_xyz.mp4\",\"description\":\"caption #hashtags\",\"schedule_type\":\"now\",\"media_type\":\"VIDEO\"}" } → { user_job_id } ``` Data fields (JSON-stringified in `data`): | Field | Type | Required | Notes | |-------|------|----------|-------| | `platform` | string | Yes | `tiktok` | | `media_url` | string | Yes | public URL to media (render URL works directly) | | `description` | string | Yes | caption, max ~2200 chars for TikTok; include hashtags inline | | `title` | string | No | post title | | `media_type` | string | No | `VIDEO` (default) \| `IMAGE` | | `schedule_type` | string | Yes | `now` \| `scheduled` | | `scheduled_time` | string | if `scheduled` | ISO 8601 UTC, must be future | | `thumbnail_url` | string | No | custom thumbnail URL | | `timezone_offset` | integer | No | minutes from UTC | ### Poll the publish job ``` GET /v1/user_jobs/{id}?agent_id={id} ``` Status flow: `pending → processing → completed | failed`. On `completed`, `result.post_id` is the platform's post ID. ### Prerequisites - Agent must have a connected TikTok account (connected via the GEN app) - Media must be publicly accessible at post time - For scheduled posts, `scheduled_time` must be in the future --- # Error Format ```json { "error": "Human-readable message", "error_code": "machine_code" } ``` | Status | Code | Meaning | |--------|------|---------| | 401 | `unauthorized` | missing or invalid API key | | 403 | `forbidden` | no permission for this resource | | 404 | `not_found` | resource doesn't exist | | 422 | `usable_gen_credit_required` | no active credits | | 422 | `agent_not_found` | invalid `agent_id` | | 422 | `insufficient_credits_for_job` | not enough credits for this generation | | 422 | `validation_error` | invalid request body | ## Common status codes | Code | Meaning | |------|---------| | 200 | Success | | 201 | Resource created | | 204 | Success, no content (used for deletes) | | 207 | Multi-Status (partial success on PATCH /core) | | 401 | Unauthorized | | 403 | Forbidden | | 404 | Not found | | 422 | Validation / credits / business-rule error | --- # Integrations - MCP Server: `npx @poweredbygen/autocontentengine-mcp-server@latest` (env `GEN_API_KEY`) - TypeScript SDK: `npm install @poweredbygen/gen-sdk` - n8n: HTTP Request node with `X-API-Key` header - OpenAPI 3.1: https://api.gen.pro/openapi.yaml (every operation carries `x-phase`) - Full Docs (5-step journey): https://api.gen.pro - Teaching llms.txt: https://api.gen.pro/llms.txt