Files
songs2vid/website/docs/api/endpoints.md
T

14 KiB
Raw Blame History

sidebar_position
sidebar_position
2

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"

Local API docs: with npm run dev:all (or npm run docs:dev) open http://localhost:3001/docs/api/overview.

Discovery

curl "$BASE_URL/api/v1"

Returns the endpoint list and requirements (no auth).

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

Field Required Notes
file Yes Multipart file
type Yes image | audio | logo | font

Allowed files (self-hosted)

type Formats Max size
image JPEG, PNG, WebP, GIF 500 MB
audio MP3, WAV, FLAC (also accepts related MIME types) 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"
  }
}
Field Notes
path Absolute path on the server — pass this into job create
filename Original filename
size Bytes
audioTags Present 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.

Self-hosted deployments unlock per-track covers, custom watermarks/fonts, and art-track layouts. Max batch size: 100.

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",
        "description": "",
        "tags": "electronic",
        "privacy": "PUBLIC",
        "categoryId": "10",
        "resolution": "1920x1080",
        "notifySubscribers": true,
        "madeForKids": false,
        "embeddable": true,
        "creativeCommons": false,
        "includeWatermark": true,
        "imagePath": "/uploads/.../track-cover.jpg",
        "layout": {
          "template": "COVER_LEFT_TEXT_RIGHT",
          "blurAmount": 60,
          "blurOpacity": 85,
          "textPadding": 48,
          "titleArtistGap": 12,
          "textOffsetX": 0,
          "textOffsetY": 0
        },
        "watermark": {
          "mode": "default",
          "fontKey": "montserrat",
          "position": "bottom-right",
          "offsetX": 24,
          "offsetY": 24
        },
        "playlistId": null
      }
    }]
  }'

Success response

{
  "jobId": "clxxxxxxxx",
  "itemCount": 1,
  "status": "PENDING",
  "playlist": null
}

playlist is set when you pass createPlaylist (see YouTube playlists).

Metadata fields

Field Type Notes
title string YouTube video title. If empty, falls back to ${artist} - ${songTitle} when those are set
songTitle string | null On-video song / track title for art-track layouts (max 120)
artist string | null On-video artist line for art-track layouts (max 80)
description string YouTube description
tags string Comma-separated (quoted tags supported)
privacy string PUBLIC | PRIVATE | UNLISTED
categoryId string YouTube category ID (see below)
resolution string One of the supported values (see below)
notifySubscribers boolean YouTube upload notify flag
madeForKids boolean COPPA / made for kids
embeddable boolean Allow embedding
creativeCommons boolean CC license vs standard YouTube
includeWatermark boolean Apply watermark settings
imagePath string | null Per-track cover (overrides job imagePath)
playlistId string | null Existing playlist ID
layout object Art-track layout (see below)
watermark object Watermark settings (see below)

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

Resolutions

Value Aspect
1920x1080 16:9
1280x720 16:9
854x480 16:9
720x720 1:1
640x360 16:9
426x240 16:9

Self-hosted allows all of these.

YouTube categories

Pass categoryId as a string ID. Common values:

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

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). For custom, upload with type=font first and set fontPath. system maps to Arial in the dashboard preview.

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

Art-track layouts

metadata.layout.template (or flat layout_template / layoutTemplate):

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

Optional fine-tuning (clamped; camelCase or snake_case):

Field Range Default Purpose
blurAmount / blur_amount 0100 55 Background boxblur intensity
blurOpacity / blur_opacity 0100 100 Blurred fill vs black
textPadding / text_padding 16120 48 Padding around cover and text
titleArtistGap / title_artist_gap 064 10 Space between title and artist
textOffsetX / text_offset_x 120120 0 Shift text block horizontally
textOffsetY / text_offset_y 120120 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 (see Video editing — Video title, song title, and artist).

Omit layout.template (or use classic letterbox) when you only want a black-padded cover. 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"}'
{
  "playlistId": "PLxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}

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.

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

Status Meaning
PENDING Queued; worker has not started
PROCESSING At least one item is encoding or uploading
COMPLETED All items succeeded
FAILED All items failed
PARTIAL Mix of completed and failed items

Item statuses

Status Meaning
PENDING Waiting in the queue
ENCODING FFmpeg is building the video
UPLOADING Uploading to YouTube
COMPLETED Live on YouTube (youtubeVideoId set)
FAILED Failed (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

Status When
400 Validation error (bad file type, invalid layout, missing fields, bad JSON)
401 Missing or invalid API key
403 YouTube not connected, or the request is not allowed for this account
404 Job not found
429 API rate limit exceeded — body includes retryAfterSeconds; header Retry-After is set

Example rate-limit body:

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

Self-hosted rate limits are effectively unlimited for normal use. See API overview.