Payment-free self-hosted builds keep full API access with optional webhookUrl callbacks and the published n8n-nodes-songs2vid package source under integrations/n8n.
12 KiB
Endpoints
Set these for the examples below:
export BASE_URL="http://localhost:3000" # local / self-hosted app
export API_KEY="s2yt_live_your_key_here"
Live HTML docs: docs.songs2vid.com/docs/api/endpoints.
Discovery
curl "$BASE_URL/api/v1"
Returns the endpoint list and requirements (no auth). No billing routes are listed or implemented.
Upload a file (two-step)
curl -X POST "$BASE_URL/api/v1/upload" \
-H "Authorization: Bearer $API_KEY" \
-F "file=@cover.jpg" \
-F "type=image"
curl -X POST "$BASE_URL/api/v1/upload" \
-H "Authorization: Bearer $API_KEY" \
-F "file=@track1.mp3" \
-F "type=audio"
# Optional: PNG watermark logo
curl -X POST "$BASE_URL/api/v1/upload" \
-H "Authorization: Bearer $API_KEY" \
-F "file=@logo.png" \
-F "type=logo"
# Optional: custom font (.ttf / .otf, max 10 MB)
curl -X POST "$BASE_URL/api/v1/upload" \
-H "Authorization: Bearer $API_KEY" \
-F "file=@Brand.ttf" \
-F "type=font"
Request fields
| Field | Required | Notes |
|---|---|---|
file |
Yes | Multipart file |
type |
Yes | image | audio | logo | font |
Allowed files
type |
Formats | Max size |
|---|---|---|
image |
JPEG, PNG, WebP, GIF | 500 MB |
audio |
MP3, WAV, FLAC | 500 MB |
logo |
PNG only | 500 MB |
font |
.ttf / .otf |
10 MB |
Response
{
"path": "/uploads/.../track.mp3",
"filename": "track.mp3",
"size": 4123456,
"audioTags": {
"title": "Song Title",
"artist": "Artist Name",
"album": "Album",
"genre": "Electronic",
"year": "2024"
}
}
audioTags is present for MP3 when tags are readable; otherwise null.
Create job / render from paths (recommended)
Self-hosted unlocks per-track covers, custom watermarks/fonts, and art-track layouts. Max batch size: 100. Watermarks are optional — nothing forces the default Songs2VID badge.
POST /api/v1/render is an n8n-friendly alias of POST /api/v1/jobs (identical body and response). GET /api/v1/render lists jobs like GET /api/v1/jobs.
Optional webhookUrl (absolute http(s) URL) makes Songs2VID POST JSON when items/jobs finish — preferred for n8n.
curl -X POST "$BASE_URL/api/v1/render" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"imagePath": "/uploads/.../cover.jpg",
"webhookUrl": "https://your-n8n.example/webhook/songs2vid-complete",
"items": [{
"audioPath": "/uploads/.../track.mp3",
"audioFilename": "track.mp3",
"metadata": {
"title": "My Artist - My Track (Official Audio)",
"songTitle": "My Track",
"artist": "My Artist",
"description": "",
"tags": "electronic",
"privacy": "PUBLIC",
"categoryId": "10",
"resolution": "1920x1080",
"notifySubscribers": true,
"madeForKids": false,
"embeddable": true,
"creativeCommons": false,
"includeWatermark": true,
"layout": {
"template": "LOWER_LEFT_COVER_TEXT",
"blurAmount": 60,
"blurOpacity": 85,
"textPadding": 48,
"titleArtistGap": 12,
"titleBold": true,
"textOffsetX": 0,
"textOffsetY": 0
},
"watermark": {
"mode": "default",
"fontKey": "montserrat",
"position": "bottom-right",
"offsetX": 24,
"offsetY": 24
},
"playlistId": null
}
}]
}'
Equivalent path: POST $BASE_URL/api/v1/jobs with the same JSON.
Success response
{
"jobId": "clxxxxxxxx",
"itemCount": 1,
"status": "PENDING",
"statusUrl": "/api/v1/jobs/clxxxxxxxx",
"webhookUrl": "https://your-n8n.example/webhook/songs2vid-complete",
"playlist": null
}
Webhook payload
event |
Meaning |
|---|---|
job.item.completed |
One track finished (youtubeVideoId set) |
job.item.failed |
One track failed (error set) |
job.completed |
All items succeeded |
job.failed |
All items failed |
job.partial |
Mix of success and failure |
Community node: n8n-nodes-songs2vid — see docs/n8n.md and docs.songs2vid.com/docs/n8n.
Legacy example without webhook (same metadata shape)
curl -X POST "$BASE_URL/api/v1/jobs" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"imagePath": "/uploads/.../cover.jpg",
"items": [{
"audioPath": "/uploads/.../track.mp3",
"audioFilename": "track.mp3",
"metadata": {
"title": "My Artist - My Track (Official Audio)",
"songTitle": "My Track",
"artist": "My Artist",
"privacy": "PUBLIC",
"categoryId": "10",
"resolution": "1920x1080",
"includeWatermark": false
}
}]
}'
Metadata fields
| Field | Type | Notes |
|---|---|---|
title |
string | YouTube video title |
songTitle |
string | null | On-video song title for art-track (max 120) |
artist |
string | null | On-video artist line (max 80) |
description |
string | YouTube description |
tags |
string | Comma-separated |
privacy |
string | PUBLIC | PRIVATE | UNLISTED |
categoryId |
string | YouTube category ID |
resolution |
string | See resolutions below |
notifySubscribers |
boolean | YouTube notify flag |
madeForKids |
boolean | Made for kids |
embeddable |
boolean | Allow embedding |
creativeCommons |
boolean | CC vs standard YouTube license |
includeWatermark |
boolean | Apply watermark settings |
imagePath |
string | null | Per-track cover |
backgroundImagePath |
string | null | Lower-corner templates only: separate blur-fill image (else cover is blurred) |
playlistId |
string | null | Existing playlist ID |
layout |
object | Art-track layout |
watermark |
object | Watermark settings |
Snake_case aliases are accepted for layout/watermark fields.
Resolutions
| Value | Aspect |
|---|---|
1920x1080 |
16:9 |
1280x720 |
16:9 |
854x480 |
16:9 |
720x720 |
1:1 |
640x360 |
16:9 |
426x240 |
16:9 |
YouTube categories
| ID | Name |
|---|---|
1 |
Film & Animation |
2 |
Autos & Vehicles |
10 |
Music |
15 |
Pets & Animals |
17 |
Sports |
19 |
Travel & Events |
20 |
Gaming |
22 |
People & Blogs |
23 |
Comedy |
24 |
Entertainment |
25 |
News & Politics |
26 |
Howto & Style |
27 |
Education |
28 |
Science & Technology |
29 |
Nonprofits & Activism |
Watermark fields
watermark.mode: none | default | text | logo
none— no overlaydefault— built-in Songs2VID badge PNG (assets/watermark.png)logo— requires priortype=logoupload; setlogoPathtext— custom string (max 80); settext
watermark.position: top-left | top-right | bottom-left | bottom-right | center
watermark.offsetX / offsetY: 0–200 (default 20)
watermark.fontKey: system | inter | montserrat | roboto | oswald | playfair | custom (styles art-track text and text watermarks)
Art-track layouts
metadata.layout.template (or flat layout_template / layoutTemplate). Valid enums are also listed on GET /api/v1 as layoutTemplates. Mirrored pairs used by the Layout Studio composition grid are listed under compositionFamilies (side: cover beside text; lower: lower corner).
| Enum | Description |
|---|---|
COVER_LEFT_TEXT_RIGHT |
Cover left, title & artist right |
COVER_TOP_TEXT_BOTTOM |
Cover top, title & artist below |
COVER_RIGHT_TEXT_LEFT |
Cover right, title & artist left |
CENTERED_COMPACT |
Centered cover + text stack |
LOWER_LEFT_COVER_TEXT |
Lower-left cover; textPadding is equal left + bottom inset (diagonal from frame corner) with title/artist to the right |
LOWER_RIGHT_COVER_TEXT |
Lower-right cover; textPadding is equal right + bottom inset (diagonal from frame corner) with title/artist to the left |
For lower-corner templates only, optional metadata.backgroundImagePath (or background_image_path) sets a separate full-frame blur fill. Upload with type=image first, then pass the returned path. The cover (imagePath / per-item metadata.imagePath) stays the sharp corner square. Omit the field to blur the cover itself (default). blurAmount / blurOpacity still apply to whichever image is used as the fill.
Optional fine-tuning (clamped; camelCase or snake_case):
| Field | Range | Default | Purpose |
|---|---|---|---|
blurAmount / blur_amount |
0–100 | 55 | Background boxblur intensity |
blurOpacity / blur_opacity |
0–100 | 100 | Blurred fill vs black |
blurFill / blur_fill |
boolean | false |
Classic letterbox only: fill bars with blurred cover (ignored for art-track templates) |
textPadding / text_padding |
16–120 | 48 | Edge inset for cover/text. On lower-corner templates this value is applied equally on both axes (left=bottom or right=bottom) so the cover corner sits on a true diagonal from the frame corner |
titleArtistGap / title_artist_gap |
0–64 | 10 | Space between title and artist |
titleBold / title_bold |
boolean | true |
Bold song title (preview + FFmpeg) |
textOffsetX / text_offset_x |
−120–120 | 0 | Shift text block horizontally |
textOffsetY / text_offset_y |
−120–120 | 0 | Shift text block vertically |
Also set metadata.songTitle (max 120) and metadata.artist (max 80) for the on-video text lines. metadata.title remains the YouTube title.
Omit layout.template for classic letterbox (black-padded cover by default). Set layout.blurFill / blur_fill to true to fill letterbox bars with a blurred cover; then blurAmount / blurOpacity apply. Free-form cover coordinates (x, y, coverX, …) and layout-level offsetX/offsetY are rejected.
Invalid template strings return 400:
{ "error": "Invalid layout template. Refer to API documentation for valid enum values." }
YouTube playlists
curl "$BASE_URL/api/v1/playlists" \
-H "Authorization: Bearer $API_KEY"
curl -X POST "$BASE_URL/api/v1/playlists" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"title":"My Album","description":"From Songs2VID","privacy":"unlisted"}'
Or pass createPlaylist on a job / batch body to create a playlist and attach all videos.
One-shot batch (small packs only)
curl -X POST "$BASE_URL/api/v1/jobs/batch" \
-H "Authorization: Bearer $API_KEY" \
-F "image=@cover.jpg" \
-F "audio=@track1.mp3" \
-F "audio=@track2.mp3" \
-F 'metadata={"defaults":{"privacy":"PUBLIC"},"items":[{"title":"Track One"},{"title":"Track Two"}]}'
Prefer two-step upload + jobs for larger packs.
Poll job status
curl "$BASE_URL/api/v1/jobs/JOB_ID" \
-H "Authorization: Bearer $API_KEY"
curl "$BASE_URL/api/v1/jobs?limit=10" \
-H "Authorization: Bearer $API_KEY"
GET /api/v1/jobs accepts limit (default 20, max 100).
Job statuses
| Status | Meaning |
|---|---|
PENDING |
Queued |
PROCESSING |
Encoding or uploading |
COMPLETED |
All items succeeded |
FAILED |
All items failed |
PARTIAL |
Mix of completed and failed |
Item statuses
| Status | Meaning |
|---|---|
PENDING |
Waiting |
ENCODING |
FFmpeg building video |
UPLOADING |
Uploading to YouTube |
COMPLETED |
Live (youtubeVideoId set) |
FAILED |
Failed (error message) |
YouTube channel daily upload caps are enforced by Google, not Songs2VID.
HTTP errors
| Status | When |
|---|---|
400 |
Validation error |
401 |
Missing or invalid API key |
403 |
YouTube not connected, or request not allowed |
404 |
Job not found |
429 |
API rate limit exceeded |
There is no 402 payment / credits status in OSS.