Skip to main content

n8n integration

Automate Songs2VID from n8n with the official community package n8n-nodes-songs2vid.

Renders run asynchronously on Songs2VID. Use a webhook (preferred) or poll job status so long FFmpeg jobs never hit HTTP timeouts in your workflow.

Also see the API overview for auth and rate limits.

:::info Cloud vs self-hosted

EditionBase URLAPI access
Cloudhttps://songs2vid.comDeveloper & Automation (€15) or Enterprise
Self-hosted OSSYour originAlways on — no paid plan

Independent Producer (€7) is Web UI only — REST calls return 403. :::


Prerequisites

  1. Songs2VID account with YouTube connected (Google OAuth + YouTube scopes)
  2. Cloud: Developer & Automation (or Enterprise) — pricing
  3. An n8n instance that can reach Songs2VID over HTTPS
  4. Self-hosted n8n: community packages enabled (N8N_COMMUNITY_PACKAGES_ENABLED=true)

1. Install n8n-nodes-songs2vid

  1. Open n8n → Settings → Community nodes
  2. Install a community node
  3. Package name:
n8n-nodes-songs2vid
  1. Confirm and restart n8n if prompted
  2. On the canvas, search for Songs2VID

npm: npmjs.com/package/n8n-nodes-songs2vid

Manual install (self-hosted)

cd ~/.n8n # or your n8n user folder
npm install n8n-nodes-songs2vid

Restart n8n afterward.

Fallback — HTTP Request nodes

If you cannot install community packages, use n8n’s built-in HTTP Request node against the same /api/v1/* paths (see Endpoints). Auth header:

Authorization: Bearer s2yt_live_your_key_here

2. Create an API key

  1. Sign in at songs2vid.com
  2. Open Dashboard → Settings
  3. Under API access, create a key
  4. Copy it once — it is shown only at creation and starts with s2yt_live_
# Optional: create via API (session cookie or existing Bearer key)
curl -X POST "https://songs2vid.com/api/v1/user/api-keys" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"n8n production"}'
caution

Treat API keys like passwords. Revoke from Settings or DELETE /api/v1/user/api-keys?id=....


3. Add credentials in n8n

Songs2VID credential (community node)

  1. Open any Songs2VID node → Credential to connect withCreate new
  2. API Key: paste s2yt_live_… (with or without the Bearer prefix)
  3. Base URL: https://songs2vid.com (no trailing slash), or your self-hosted origin

The node always sends Authorization: Bearer …. There is no X-API-Key header on the server.

Header Auth (HTTP Request fallback)

FieldValue
NameAuthorization
ValueBearer s2yt_live_…

Attach that credential to every Songs2VID HTTP Request node.


4. Node operations (1:1 with the API)

Every public REST endpoint is mapped. Optional create-render fields (webhook, layout, watermark, playlist, multi-track JSON, …) live under Additional Fields.

ResourceOperationMethodPath
DiscoveryGet API CatalogGET/api/v1
FileUploadPOST/api/v1/upload
RenderCreate RenderPOST/api/v1/render
RenderCreate Job (alias)POST/api/v1/jobs
RenderCreate BatchPOST/api/v1/jobs/batch
RenderGet Render StatusGET/api/v1/jobs/:id
RenderList RendersGET/api/v1/jobs or /api/v1/render
PlaylistListGET/api/v1/playlists
PlaylistCreatePOST/api/v1/playlists
API KeyList / Create / DeleteGET/POST/DELETE/api/v1/user/api-keys
note

There is no Delete Render endpoint. Finished videos live on YouTube; jobs are not deleted via REST.

Full field reference (metadata, layouts, watermarks, errors): Endpoints.


flowchart LR
A[New audio in Drive/Dropbox] --> B[Download binary]
B --> C[Upload cover]
B --> D[Upload audio]
C --> E[Create Render]
D --> E
E --> F[Webhook or poll]
F --> G[YouTube live]

With the community node

  1. Trigger — Google Drive / Dropbox File Created (filter .mp3 / .wav / .flac)
  2. Download binaries into the item (cover, audio)
  3. Songs2VID → File → Upload — binary property cover, type Image
  4. Songs2VID → File → Upload — binary property audio, type Audio
  5. Songs2VID → Render → Create Render
    • imagePath / audioPath / audioFilename from the upload responses
    • Privacy / resolution required
    • Additional Fields → Webhook URL = your n8n Webhook Production URL
  6. Separate branch: Webhook node → filter event → Discord / Notion / etc.

Equivalent JSON body (HTTP Request)

{
"imagePath": "/uploads/.../cover.jpg",
"webhookUrl": "https://your-n8n.example/webhook/songs2vid-complete",
"items": [
{
"audioPath": "/uploads/.../track.mp3",
"audioFilename": "track.mp3",
"metadata": {
"title": "Artist - Track (Official Audio)",
"songTitle": "Track",
"artist": "Artist",
"privacy": "UNLISTED",
"categoryId": "10",
"resolution": "1920x1080",
"includeWatermark": false
}
}
]
}
tip

Upload a shared cover once and reuse its path, or set a per-track cover via Additional Fields → Per-track Cover Path (metadata.imagePath).


6. Webhooks vs polling

Renders can take minutes. Do not block a single HTTP call waiting for the finished video.

Webhook (preferred)

  1. Add an n8n Webhook node (POST), path e.g. songs2vid-complete
  2. Activate the workflow and copy the Production URL
  3. Pass that URL as webhookUrl on Create Render
  4. Songs2VID POSTs:
{
"event": "job.completed",
"jobId": "clxxxxxxxx",
"status": "COMPLETED",
"itemId": "clitemxxx",
"youtubeVideoId": "dQw4w9WgXcQ",
"error": null,
"completedAt": "2026-08-08T12:34:56.000Z"
}
eventMeaning
job.item.completedOne track finished (youtubeVideoId set)
job.item.failedOne track failed (error set)
job.completedAll items succeeded
job.failedAll items failed
job.partialMix of success and failure

Poll status

Create Render returns:

{
"jobId": "clxxxxxxxx",
"status": "PENDING",
"statusUrl": "/api/v1/jobs/clxxxxxxxx",
"webhookUrl": "https://..."
}

Loop Wait (15–30s) + Get Render Status until COMPLETED, FAILED, or PARTIAL. Prefer webhooks when n8n is reachable from the public internet.


7. Copy-paste workflow template

Import via ⋯ → Import from File / URL, or copy below.

  1. Create Header Auth (Authorization = Bearer s2yt_live_…) — or swap HTTP nodes for native Songs2VID nodes after installing the package
  2. Provide binary fields cover and audio (or replace the Manual Trigger with Drive/Dropbox)
  3. Set env SONGS2VID_WEBHOOK_URL to the Webhook node’s Production URL (or paste it into Create Render)

Download workflow JSON · mirror

Preview workflow JSON
{
  "name": "Songs2VID — Audio → Render → YouTube (full)",
  "meta": {
    "templateCredsSetupCompleted": false,
    "instanceId": "songs2vid-docs-template-v2"
  },
  "nodes": [
    {
      "parameters": {},
      "id": "manual-trigger",
      "name": "When clicking ‘Test workflow’",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        0,
        0
      ]
    },
    {
      "parameters": {
        "path": "songs2vid-complete",
        "httpMethod": "POST",
        "responseMode": "onReceived",
        "options": {}
      },
      "id": "webhook-complete",
      "name": "Songs2VID Job Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        0,
        360
      ],
      "webhookId": "songs2vid-complete"
    },
    {
      "parameters": {
        "content": "## Inputs\nProvide binary fields on the Manual Trigger item (or replace with Drive/Dropbox download):\n- `cover` — JPEG/PNG cover art\n- `audio` — MP3/WAV/FLAC/…\n\nCredential: Header Auth → Name `Authorization`, Value `Bearer s2yt_live_…`",
        "height": 220,
        "width": 320
      },
      "id": "sticky-inputs",
      "name": "Setup",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -320,
        -40
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $env.SONGS2VID_BASE_URL || 'https://songs2vid.com' }}/api/v1/upload",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "contentType": "multipart-form-data",
        "bodyParameters": {
          "parameters": [
            {
              "parameterType": "formBinaryData",
              "name": "file",
              "inputDataFieldName": "cover"
            },
            {
              "name": "type",
              "value": "image"
            }
          ]
        },
        "options": {}
      },
      "id": "upload-cover",
      "name": "Upload Cover Image",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        280,
        0
      ],
      "credentials": {
        "httpHeaderAuth": {
          "id": "REPLACE_ME",
          "name": "Songs2VID API Key"
        }
      }
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $env.SONGS2VID_BASE_URL || 'https://songs2vid.com' }}/api/v1/upload",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "contentType": "multipart-form-data",
        "bodyParameters": {
          "parameters": [
            {
              "parameterType": "formBinaryData",
              "name": "file",
              "inputDataFieldName": "audio"
            },
            {
              "name": "type",
              "value": "audio"
            }
          ]
        },
        "options": {}
      },
      "id": "upload-audio",
      "name": "Upload Audio",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        520,
        0
      ],
      "credentials": {
        "httpHeaderAuth": {
          "id": "REPLACE_ME",
          "name": "Songs2VID API Key"
        }
      }
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $env.SONGS2VID_BASE_URL || 'https://songs2vid.com' }}/api/v1/render",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"imagePath\": \"{{ $('Upload Cover Image').item.json.path }}\",\n  \"webhookUrl\": \"{{ $('Songs2VID Job Webhook').params?.webhookUrl || $env.SONGS2VID_WEBHOOK_URL || '' }}\",\n  \"items\": [{\n    \"audioPath\": \"{{ $('Upload Audio').item.json.path }}\",\n    \"audioFilename\": \"{{ $('Upload Audio').item.json.filename }}\",\n    \"metadata\": {\n      \"title\": \"{{ $('Upload Audio').item.json.audioTags?.title || $('Upload Audio').item.json.filename }}\",\n      \"songTitle\": \"{{ $('Upload Audio').item.json.audioTags?.title || '' }}\",\n      \"artist\": \"{{ $('Upload Audio').item.json.audioTags?.artist || '' }}\",\n      \"description\": \"Uploaded via n8n + Songs2VID\",\n      \"tags\": \"music,songs2vid\",\n      \"privacy\": \"UNLISTED\",\n      \"categoryId\": \"10\",\n      \"resolution\": \"1920x1080\",\n      \"notifySubscribers\": false,\n      \"madeForKids\": false,\n      \"embeddable\": true,\n      \"creativeCommons\": false,\n      \"includeWatermark\": false\n    }\n  }]\n}",
        "options": {}
      },
      "id": "create-render",
      "name": "Create Render",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        760,
        0
      ],
      "credentials": {
        "httpHeaderAuth": {
          "id": "REPLACE_ME",
          "name": "Songs2VID API Key"
        }
      }
    },
    {
      "parameters": {
        "method": "GET",
        "url": "={{ $env.SONGS2VID_BASE_URL || 'https://songs2vid.com' }}{{ $json.statusUrl }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {}
      },
      "id": "poll-status",
      "name": "Get Render Status (optional poll)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1000,
        0
      ],
      "credentials": {
        "httpHeaderAuth": {
          "id": "REPLACE_ME",
          "name": "Songs2VID API Key"
        }
      },
      "disabled": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "job-done",
              "leftValue": "={{ $json.body?.event || $json.event }}",
              "rightValue": "job.completed",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            },
            {
              "id": "item-done",
              "leftValue": "={{ $json.body?.event || $json.event }}",
              "rightValue": "job.item.completed",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "or"
        },
        "options": {}
      },
      "id": "if-completed",
      "name": "Render Completed?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        280,
        360
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "job-fail",
              "leftValue": "={{ $json.body?.event || $json.event }}",
              "rightValue": "job.failed",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            },
            {
              "id": "item-fail",
              "leftValue": "={{ $json.body?.event || $json.event }}",
              "rightValue": "job.item.failed",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "or"
        },
        "options": {}
      },
      "id": "if-failed",
      "name": "Render Failed?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        280,
        560
      ]
    },
    {
      "parameters": {
        "content": "## YouTube live\nVideo ID: `{{ $json.body?.youtubeVideoId || $json.youtubeVideoId }}`\n\nOpen: https://youtu.be/{{ $json.body?.youtubeVideoId || $json.youtubeVideoId }}\n\nJob: `{{ $json.body?.jobId || $json.jobId }}`",
        "height": 200,
        "width": 360
      },
      "id": "sticky-success",
      "name": "Success",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        560,
        320
      ]
    },
    {
      "parameters": {
        "content": "## Failed\nError: `{{ $json.body?.error || $json.error }}`\n\nPoll: `GET {{ $env.SONGS2VID_BASE_URL || 'https://songs2vid.com' }}/api/v1/jobs/{{ $json.body?.jobId || $json.jobId }}`",
        "height": 200,
        "width": 360
      },
      "id": "sticky-fail",
      "name": "Failure",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        560,
        520
      ]
    }
  ],
  "connections": {
    "When clicking ‘Test workflow’": {
      "main": [
        [
          {
            "node": "Upload Cover Image",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Upload Cover Image": {
      "main": [
        [
          {
            "node": "Upload Audio",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Upload Audio": {
      "main": [
        [
          {
            "node": "Create Render",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Render": {
      "main": [
        [
          {
            "node": "Get Render Status (optional poll)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Songs2VID Job Webhook": {
      "main": [
        [
          {
            "node": "Render Completed?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Render Completed?": {
      "main": [
        [
          {
            "node": "Success",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Render Failed?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Render Failed?": {
      "main": [
        [
          {
            "node": "Failure",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "staticData": null,
  "tags": [
    {
      "name": "songs2vid"
    },
    {
      "name": "n8n"
    },
    {
      "name": "youtube"
    }
  ],
  "triggerCount": 0,
  "updatedAt": "2026-08-08T00:00:00.000Z",
  "versionId": "2"
}
note

The shipped template uses HTTP Request + Webhook so it works before the community package is installed. After installing n8n-nodes-songs2vid, swap those HTTP nodes for native Songs2VID operations.


Plans, quotas & errors

PlanREST APIMonthly rendersAudio encode
FreeNo3 (Web UI)192 kbps AAC
Independent Producer (€7)No — 40330 (Web UI)320 kbps
Developer & Automation (€15)Yes60 (UI + API)320 kbps · priority queue
EnterpriseYesCustom320 kbps · priority queue
StatusMeaning
401Missing/invalid API key
403Not on Developer+ · YouTube not connected
402Monthly quota / credits exhausted
429Rate limit (60 req/min per key) — honor Retry-After

Troubleshooting

SymptomFix
Package not found in Community nodesConfirm the name is exactly n8n-nodes-songs2vid; on self-hosted enable N8N_COMMUNITY_PACKAGES_ENABLED
Node missing after installRestart n8n; hard-refresh the editor
403 Developer plan messageUpgrade to Developer & Automation; Producer/Free cannot use REST
403 YouTube not connectedSign in on the dashboard and reconnect Google with YouTube scopes
Webhook never firesUse the Production webhook URL, activate the workflow, ensure Songs2VID can reach it over HTTPS
Upload / create validation errorsCheck required fields and enums in Endpoints

Next steps