{
  "info": {
    "name": "ViddyFlow API",
    "description": "Complete API collection for ViddyFlow highlight generation.\n\nGet your API key: https://app.viddyflow.com/api-access\nAPI reference: https://viddyflow.com/docs/api\nWebhooks: https://viddyflow.com/docs/webhooks\n\n**Base URL:** `https://api.viddyflow.com`\n**Auth:** every request sends your key in the `X-API-Key` header. This collection sets it for you from the `apiKey` variable.\n**Source:** the API accepts public **Twitch VOD** URLs. Direct file uploads, background music, and branding presets are dashboard-only features (their endpoints are session-authenticated).\n\n**Rate limits:** `POST /jobs` is capped at 10 requests/minute per IP. Other endpoints are not explicitly limited.\n\n**Getting started**\n1. Set the `apiKey` collection variable to your key.\n2. Run \"Create Job\", which saves the returned job id into the `jobId` variable automatically.\n3. Poll \"Get Job Status\", or set `webhook_url` on the job and let ViddyFlow call you.\n4. When status is `completed`, run \"Get Job Results\" for per-clip download links.\n",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "auth": {
    "type": "apikey",
    "apikey": [
      {
        "key": "key",
        "value": "X-API-Key",
        "type": "string"
      },
      {
        "key": "value",
        "value": "{{apiKey}}",
        "type": "string"
      },
      {
        "key": "in",
        "value": "header",
        "type": "string"
      }
    ]
  },
  "variable": [
    {
      "key": "baseUrl",
      "value": "https://api.viddyflow.com",
      "type": "string"
    },
    {
      "key": "apiKey",
      "value": "replace-with-your-key",
      "type": "string"
    },
    {
      "key": "jobId",
      "value": "",
      "type": "string"
    }
  ],
  "item": [
    {
      "name": "Create a job",
      "description": "Four ready-to-run shapes of POST /jobs. Each saves the returned id into the {{jobId}} collection variable.",
      "item": [
        {
          "name": "Highlight reel + shorts (full example)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/jobs",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "jobs"
              ]
            },
            "description": "Every field the API supports, with a highlight reel plus 5 vertical shorts.\n\n`run_overrides` is keyed by run profile name and only applies to profiles listed in `run_profiles`.\n\nOmit `vod_length_hours` and ViddyFlow probes the VOD's real duration for you.\n\n**Note:** `num_shorts` is capped by your plan (3 on the free trial or before any purchase, 5 on pay-as-you-go, 10 on Pro/Studio). Asking for more is clamped down silently rather than rejected, and you are charged for the clamped count.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"vod_url\": \"https://twitch.tv/videos/123456789\",\n  \"streamer_name\": \"SampleCreator\",\n  \"game_name\": \"Any Game\",\n  \"streamer_pronouns\": \"they/them\",\n  \"streamer_twitch_url\": \"https://twitch.tv/samplecreator\",\n  \"streamer_twitter_url\": \"https://twitter.com/samplecreator\",\n  \"webhook_url\": \"https://hooks.example.com/viddyflow/status\",\n  \"stream_language\": \"en\",\n  \"metadata_language\": \"en\",\n  \"model_preset\": \"auto_balance\",\n  \"run_profiles\": [\n    \"balanced\"\n  ],\n  \"run_overrides\": {\n    \"balanced\": {\n      \"audience_weight\": 0.5,\n      \"streamer_weight\": 0.5,\n      \"similarity_threshold\": 0.7,\n      \"target_duration_minutes\": 8\n    }\n  },\n  \"final_video_length_minutes\": 8,\n  \"num_shorts\": 5,\n  \"burn_captions\": true,\n  \"parameters\": {\n    \"min_highlight_duration\": 30,\n    \"max_highlight_duration\": 90,\n    \"cold_open_enabled\": true,\n    \"cold_open_max_clips\": 4,\n    \"cold_open_snippet_duration\": 5,\n    \"reel_title_card_enabled\": true,\n    \"shorts_title_card_enabled\": true,\n    \"shorts_crop_mode\": \"crop\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Save the new job id so the other requests in this collection can use it.",
                  "if (pm.response.code === 200 || pm.response.code === 201) {",
                  "    const data = pm.response.json();",
                  "    if (data.id) {",
                  "        pm.collectionVariables.set('jobId', data.id);",
                  "        console.log('Saved jobId =', data.id);",
                  "    }",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "Shorts only (no compilation)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/jobs",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "jobs"
              ]
            },
            "description": "Set `compilation_enabled: false` to skip the highlight reel entirely and pay no generation credits for one.\n\nRequires `num_shorts` > 0, and `final_video_length_minutes` is forced to 0 server-side.\n\nA run profile is still required: it decides which moments the shorts are cut from. Only one profile runs for a shorts-only job.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"vod_url\": \"https://twitch.tv/videos/123456789\",\n  \"streamer_name\": \"SampleCreator\",\n  \"game_name\": \"Any Game\",\n  \"webhook_url\": \"https://hooks.example.com/viddyflow/status\",\n  \"run_profiles\": [\n    \"balanced\"\n  ],\n  \"compilation_enabled\": false,\n  \"final_video_length_minutes\": 0,\n  \"num_shorts\": 6,\n  \"burn_captions\": true,\n  \"parameters\": {\n    \"shorts_crop_mode\": \"crop\",\n    \"shorts_title_card_enabled\": true\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Save the new job id so the other requests in this collection can use it.",
                  "if (pm.response.code === 200 || pm.response.code === 201) {",
                  "    const data = pm.response.json();",
                  "    if (data.id) {",
                  "        pm.collectionVariables.set('jobId', data.id);",
                  "        console.log('Saved jobId =', data.id);",
                  "    }",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "Shorts with facecam split framing",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/jobs",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "jobs"
              ]
            },
            "description": "`shorts_crop_mode: \"split\"` stacks your facecam over the gameplay in the vertical frame.\n\nThe facecam and gameplay regions default to the common bottom-right facecam layout, so this works with no coordinates at all. To place them precisely, use the dashboard's visual region editor: it writes the same job for you.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"vod_url\": \"https://twitch.tv/videos/123456789\",\n  \"streamer_name\": \"SampleCreator\",\n  \"game_name\": \"Any Game\",\n  \"run_profiles\": [\n    \"balanced\"\n  ],\n  \"final_video_length_minutes\": 8,\n  \"num_shorts\": 4,\n  \"parameters\": {\n    \"shorts_crop_mode\": \"split\",\n    \"shorts_split_ratio\": 0.5,\n    \"shorts_facecam_frame_enabled\": true,\n    \"shorts_facecam_frame_color\": \"white\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Save the new job id so the other requests in this collection can use it.",
                  "if (pm.response.code === 200 || pm.response.code === 201) {",
                  "    const data = pm.response.json();",
                  "    if (data.id) {",
                  "        pm.collectionVariables.set('jobId', data.id);",
                  "        console.log('Saved jobId =', data.id);",
                  "    }",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "Minimal",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/jobs",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "jobs"
              ]
            },
            "description": "The smallest valid job. Everything not sent falls back to the account defaults: one balanced highlight reel, no shorts.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"vod_url\": \"https://twitch.tv/videos/123456789\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Save the new job id so the other requests in this collection can use it.",
                  "if (pm.response.code === 200 || pm.response.code === 201) {",
                  "    const data = pm.response.json();",
                  "    if (data.id) {",
                  "        pm.collectionVariables.set('jobId', data.id);",
                  "        console.log('Saved jobId =', data.id);",
                  "    }",
                  "}"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "Track a job",
      "item": [
        {
          "name": "List jobs",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/jobs?page=1&page_size=20",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "jobs"
              ],
              "query": [
                {
                  "key": "page",
                  "value": "1"
                },
                {
                  "key": "page_size",
                  "value": "20"
                },
                {
                  "key": "status",
                  "value": "completed",
                  "disabled": true
                }
              ]
            },
            "description": "Paginated list of your jobs, newest first.\n\nOptional `status` filter: `pending`, `running`, `completed`, `failed`.\nOptional `search` matches streamer name, game, or VOD URL."
          },
          "response": []
        },
        {
          "name": "Get job status",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/jobs/{{jobId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "jobs",
                "{{jobId}}"
              ]
            },
            "description": "Current status of one job: `pending`, `running`, `completed`, or `failed`.\n\nPoll this, or set `webhook_url` when creating the job and let ViddyFlow call you instead."
          },
          "response": []
        },
        {
          "name": "Get job results",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/jobs/{{jobId}}/results",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "jobs",
                "{{jobId}}",
                "results"
              ]
            },
            "description": "Per-clip download links once the job is completed.\n\n`deliverables` is the flat, ready-to-consume list: each entry has a type, title, duration and a presigned `url`. Links expire, see `links_expire_at`. Re-request this endpoint for fresh ones."
          },
          "response": []
        },
        {
          "name": "Cancel job",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/jobs/{{jobId}}/cancel",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "jobs",
                "{{jobId}}",
                "cancel"
              ]
            },
            "description": "Cancel a pending or running job. Credits are refunded on a tiered policy based on how far the job got.\n\nThe job is then reported as `failed` with a \"cancelled by user\" message: there is no separate `cancelled` status. Anything already `completed` or `failed` returns 400."
          },
          "response": []
        }
      ]
    },
    {
      "name": "Download",
      "item": [
        {
          "name": "Download everything (ZIP)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/jobs/{{jobId}}/download-all",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "jobs",
                "{{jobId}}",
                "download-all"
              ]
            },
            "description": "Streams every artifact for the job as a single ZIP: the reel, the shorts, their thumbnails, metadata and caption sidecars.\n\nBuilt on demand, so nothing is stored twice. In Postman use Send and Download to save the file."
          },
          "response": []
        }
      ]
    },
    {
      "name": "Utilities",
      "item": [
        {
          "name": "Probe VOD metadata",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/videos/metadata?vod_url=https://twitch.tv/videos/123456789",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "videos",
                "metadata"
              ],
              "query": [
                {
                  "key": "vod_url",
                  "value": "https://twitch.tv/videos/123456789"
                }
              ]
            },
            "description": "Check a Twitch VOD before spending credits: returns its title and duration, and surfaces why it is unusable (private, subscriber-only, deleted, or still live)."
          },
          "response": []
        },
        {
          "name": "Submit job feedback",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/jobs/{{jobId}}/feedback",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "jobs",
                "{{jobId}}",
                "feedback"
              ]
            },
            "description": "Tell ViddyFlow whether a finished job's picks were good. This feeds highlight-detection tuning.\n\nOnly works on a `completed` job. `reason` is optional and applies to negative votes only: `wrong_moments`, `timing_off`, `needs_editing`, or `other`. Choosing `other` requires `notes`.\n\nReturns 204 with no body. Re-submitting overwrites the previous vote.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"positive\": false,\n  \"reason\": \"wrong_moments\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        }
      ]
    }
  ]
}
