Skip to main content

Endpoints

Set these for the examples below:

export BASE_URL="https://songs2vid.com" # cloud SaaS (Developer+)
# export BASE_URL="http://localhost:3000" # local / self-hosted
export API_KEY="s2yt_live_your_key_here"

Auth on every protected call: -H "Authorization: Bearer $API_KEY" (no X-API-Key header).

Local docs: npm run docs:devhttp://localhost:3001/docs/api/overview. Automation: n8n integration.

Discovery

curl "$BASE_URL/api/v1"

Returns the endpoint list and requirements (no auth).

Manage API keys

Developer+ only. Auth: session cookie or Bearer API key. UI: Dashboard → Settings.

# List
curl "$BASE_URL/api/v1/user/api-keys" \
-H "Authorization: Bearer $API_KEY"

# Create (name optional; key shown once)
curl -X POST "$BASE_URL/api/v1/user/api-keys" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"n8n production"}'
{
"apiKey": "s2yt_live_…",
"prefix": "s2yt_live_…",
"name": "n8n production",
"message": "Copy this key now. It will not be shown again."
}
# Revoke one / all
curl -X DELETE "$BASE_URL/api/v1/user/api-keys?id=KEY_ID" \
-H "Authorization: Bearer $API_KEY"
curl -X DELETE "$BASE_URL/api/v1/user/api-keys" \
-H "Authorization: Bearer $API_KEY"
StatusBody
401Missing/invalid key when not signed in
403{ "error": "REST API access requires the Developer & Automation plan." }

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) for art-track / text watermark
curl -X POST "$BASE_URL/api/v1/upload" \
-H "Authorization: Bearer $API_KEY" \
-F "file=@Brand.ttf" \
-F "type=font"

Request fields

FieldRequiredNotes
fileYesMultipart file
typeYesimage | audio | logo | font

Allowed files

Limits depend on plan (cloud). Self-hosted uses the high OSS ceilings.

typeFormatsTypical max (Developer)
imageJPEG, PNG, WebP, GIF50 MB
audioMP3, WAV, FLAC, AAC, OGG, M4A500 MB
logoPNG only50 MB
font.ttf / .otf10 MB

Free cloud accounts: audio MP3 only, ≤50 MB, encode 192 kbps. Paid plans encode 320 kbps AAC (bitrate is not a request field).

Response

{
"path": "/uploads/.../track.mp3",
"filename": "track.mp3",
"size": 4123456,
"audioTags": {
"title": "Song Title",
"artist": "Artist Name",
"album": "Album",
"genre": "Electronic",
"year": "2024"
}
}
FieldNotes
pathAbsolute path on the server — pass this into job create
filenameOriginal filename
sizeBytes
audioTagsPresent for MP3 when tags are readable; otherwise null. Fields may be omitted when missing in the file

Upload the shared cover once (type=image), each audio (type=audio), optionally a PNG logo (type=logo), and optionally a custom font (type=font) for art-track typography and/or text watermarks.

Use the exact path strings returned by upload. Add one items[] entry per track.

POST /api/v1/render is an n8n-friendly alias of POST /api/v1/jobs (identical body and response). GET /api/v1/render likewise lists jobs like GET /api/v1/jobs.

Cloud Developer unlocks per-track covers, custom watermarks/fonts, art-track layouts, and priority queue. Max batch size: 10 (Developer) / 50 (Enterprise) / 100 (self-hosted).

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": false,
"imagePath": "/uploads/.../track-cover.jpg",
"layout": {
"template": "LOWER_LEFT_COVER_TEXT",
"blurAmount": 60,
"blurOpacity": 85,
"textPadding": 48,
"titleArtistGap": 12,
"titleBold": true,
"textOffsetX": 0,
"textOffsetY": 0
},
"watermark": {
"mode": "none",
"fontKey": "montserrat",
"position": "bottom-right",
"offsetX": 24,
"offsetY": 24
},
"playlistId": null
}
}]
}'

Equivalent path: POST $BASE_URL/api/v1/jobs with the same JSON.

Top-level job fields

FieldRequiredNotes
imagePathYesShared cover path from upload
itemsYes≥1 track
webhookUrlNoAbsolute http(s) URL — Songs2VID POSTs JSON on item/job terminal states (n8n)
createPlaylistNo{ title, description?, privacy? } — creates a playlist and attaches all videos

Success response

{
"jobId": "clxxxxxxxx",
"itemCount": 1,
"status": "PENDING",
"statusUrl": "/api/v1/jobs/clxxxxxxxx",
"webhookUrl": "https://your-n8n.example/webhook/songs2vid-complete",
"playlist": null
}

Poll with GET $BASE_URL + statusUrl. playlist is set when you pass createPlaylist (see YouTube playlists).

Create-job errors

StatusWhen
400Validation (missing files, bad layout, invalid webhookUrl, …)
401Missing/invalid API key
403Not on Developer+ · YouTube not connected · Pro-only feature (code: "PREMIUM_REQUIRED")
402Monthly quota / credits exhausted
429Rate limit

Example Producer/Free API attempt:

{ "error": "REST API access requires the Developer & Automation plan." }

Example bad webhook:

{ "error": "Invalid webhookUrl. Provide an absolute http(s) URL for n8n callbacks." }

Metadata fields

FieldTypeNotes
titlestringYouTube video title. If empty, falls back to ${artist} - ${songTitle} when those are set
songTitlestring | nullOn-video song / track title for art-track layouts (max 120)
artiststring | nullOn-video artist line for art-track layouts (max 80)
descriptionstringYouTube description
tagsstringComma-separated (quoted tags supported)
privacystringPUBLIC | PRIVATE | UNLISTED
categoryIdstringYouTube category ID (see below)
resolutionstringOne of the supported values (see below)
notifySubscribersbooleanYouTube upload notify flag
madeForKidsbooleanCOPPA / made for kids
embeddablebooleanAllow embedding
creativeCommonsbooleanCC license vs standard YouTube
includeWatermarkbooleanApply watermark settings
imagePathstring | nullPer-track cover (overrides job imagePath)
backgroundImagePathstring | nullLower-corner templates only: separate blur-fill image (else cover is blurred)
playlistIdstring | nullExisting playlist ID
layoutobjectArt-track layout (see below)
watermarkobjectWatermark settings (see below)

Snake_case aliases are accepted for layout/watermark fields (e.g. blur_amount, layout_template).

Resolutions

ValueAspect
1920x108016:9
1280x72016:9
854x48016:9
720x7201:1
640x36016:9
426x24016:9

Self-hosted allows all of these. Cloud Free tops out at 720p (1280x720); Developer/Producer allow 1080p.

YouTube categories

Pass categoryId as a string ID. Common values:

IDName
1Film & Animation
2Autos & Vehicles
10Music
15Pets & Animals
17Sports
19Travel & Events
20Gaming
22People & Blogs
23Comedy
24Entertainment
25News & Politics
26Howto & Style
27Education
28Science & Technology
29Nonprofits & Activism

Official reference: YouTube Data API — VideoCategories.

Watermark fields

watermark.position: top-left | top-right | bottom-left | bottom-right | center.

watermark.mode: none | default | text | logo

  • default — built-in Songs2VID badge PNG (assets/watermark.png), scaled to ~42% of frame width (matches Layout Studio preview)
  • logo — requires prior type=logo upload; set logoPath (same width scaling)
  • text — custom string (max 80); set text

watermark.offsetX / offsetY: 0200 (default 20) — pixels from the chosen anchor.

watermark.fontKey: system | inter | montserrat | roboto | oswald | playfair | custom. Styles art-track song title / artist and text watermarks (same .ttf files in preview and FFmpeg). system uses bundled Arimo in preview and encode.

For a full walkthrough of composition controls, see Video editing.

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).

EnumDescription
COVER_LEFT_TEXT_RIGHTCover left, title & artist right
COVER_TOP_TEXT_BOTTOMCover top, title & artist below
COVER_RIGHT_TEXT_LEFTCover right, title & artist left
CENTERED_COMPACTCentered cover + text stack
LOWER_LEFT_COVER_TEXTLower-left cover; textPadding is equal left + bottom inset (diagonal from frame corner) with title/artist to the right
LOWER_RIGHT_COVER_TEXTLower-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):

FieldRangeDefaultPurpose
blurAmount / blur_amount0–10055Background boxblur intensity
blurOpacity / blur_opacity0–100100Blurred fill vs black
blurFill / blur_fillbooleanfalseClassic letterbox only: fill bars with blurred cover (ignored for art-track templates)
textPadding / text_padding16–12048Edge 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_gap0–6410Space between title and artist
titleBold / title_boldbooleantrueBold song title (same in preview + FFmpeg)
textOffsetX / text_offset_x−120–1200Shift text block horizontally
textOffsetY / text_offset_y−120–1200Shift 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 (see Video editing — Video title, song title, and artist).

Omit layout.template (or use classic letterbox) when you only want a black-padded cover. 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 (use textOffsetX/textOffsetY instead; watermark offsets stay under watermark).

Invalid template strings return 400:

{ "error": "Invalid layout template. Refer to API documentation for valid enum values." }

YouTube playlists

List existing playlists, create a new one, or create one inline when starting a job. Pass playlistId in item metadata / batch defaults, or use createPlaylist to make a playlist and attach all videos to it.

Privacy may be public, unlisted, or private. If playlist permission was just added, sign out and sign in again so OAuth includes youtube.force-ssl.

YouTube playlist API reference: Playlists: insert.

# List playlists
curl "$BASE_URL/api/v1/playlists" \
-H "Authorization: Bearer $API_KEY"
# Create a playlist
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"}'
{
"playlist": {
"id": "PLxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"title": "My Album",
"itemCount": 0,
"privacy": "unlisted"
}
}

Or create one inline with a job / batch request:

{
"createPlaylist": {
"title": "My Album",
"description": "Uploaded via Songs2VID",
"privacy": "private"
},
"defaults": { "privacy": "PUBLIC" },
"items": [{ "title": "Track One" }]
}

One-shot batch (small packs only)

Upload one cover image and a few audio files in a single multipart request. Not recommended for large batches — use two-step if you see FormData parse errors.

note

Batch create does not accept webhookUrl today. Prefer POST /api/v1/render with paths if you need n8n callbacks.

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={"createPlaylist":{"title":"My Album","privacy":"unlisted"},"defaults":{"privacy":"PUBLIC"},"items":[{"title":"Track One"},{"title":"Track Two"}]}'

Optional metadata JSON supports defaults applied to every item and per-item overrides in items. Item order should match the order of audio files.

When item metadata is omitted, batch defaults include privacy PUBLIC, resolution 1920x1080, and watermark off unless overridden in defaults. Per-item title is the YouTube title (falls back from the audio filename when omitted). Pass songTitle and artist in items[] or defaults when using art-track layouts.

Multipart tips

  • Do not set Content-Type manually for multipart; the client must include the boundary
  • In Postman: Body → form-data; each audio field key must be exactly audio (type File)
  • If a file field shows a warning, re-select the file from disk

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 response

{
"id": "clxxxxxxxx",
"status": "PROCESSING",
"createdAt": "2026-07-25T12:00:00.000Z",
"completedAt": null,
"items": [
{
"id": "clitemxxx",
"audioFilename": "track.mp3",
"title": "My Track",
"description": "",
"tags": "electronic",
"privacy": "PUBLIC",
"categoryId": "10",
"resolution": "1920x1080",
"status": "ENCODING",
"youtubeVideoId": null,
"error": null
}
]
}

Job statuses

StatusMeaning
PENDINGQueued; worker has not started
PROCESSINGAt least one item is encoding or uploading
COMPLETEDAll items succeeded
FAILEDAll items failed
PARTIALMix of completed and failed items

Item statuses

StatusMeaning
PENDINGWaiting in the queue
ENCODINGFFmpeg is building the video
UPLOADINGUploading to YouTube
COMPLETEDLive on YouTube (youtubeVideoId set)
FAILEDFailed (error contains a message)

Pipeline notes

  • Each item is encoded, then uploaded; the local MP4 is removed after a successful upload
  • Queue retries: 2 attempts with exponential backoff (5s base)
  • Worker concurrency: 2 items in parallel
  • On item failure, reserved allowance for that item is released

YouTube upload limits (channel daily caps, etc.) are enforced by Google, not Songs2VID. See YouTube Data API — Quota and compliance.

HTTP errors

StatusWhen
400Validation error (bad file type, invalid layout, missing fields, bad JSON, invalid webhookUrl)
401Missing or invalid API key — "Missing API key. Use Authorization: Bearer <your_api_key>" / "Invalid API key"
402Quota / credits exhausted on job create
403YouTube not connected · not on Developer+ · Pro-only feature (PREMIUM_REQUIRED)
404Job not found
429API rate limit exceeded — body includes retryAfterSeconds; header Retry-After is set

Exact Developer-gate message:

{ "error": "REST API access requires the Developer & Automation plan." }

Example rate-limit body:

{
"error": "API rate limit exceeded. Try again shortly.",
"retryAfterSeconds": 42
}

Cloud default rate limit: 60 requests / minute / API key. Self-hosted is effectively unlimited for normal use. See API overview and n8n.