# Endpoints Set these for the examples below: ```bash 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](https://docs.songs2vid.com/docs/api/endpoints). ## Discovery ```bash 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) ```bash curl -X POST "$BASE_URL/api/v1/upload" \ -H "Authorization: Bearer $API_KEY" \ -F "file=@cover.jpg" \ -F "type=image" ``` ```bash curl -X POST "$BASE_URL/api/v1/upload" \ -H "Authorization: Bearer $API_KEY" \ -F "file=@track1.mp3" \ -F "type=audio" ``` ```bash # Optional: PNG watermark logo curl -X POST "$BASE_URL/api/v1/upload" \ -H "Authorization: Bearer $API_KEY" \ -F "file=@logo.png" \ -F "type=logo" ``` ```bash # 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 ```json { "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 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. ```bash 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, "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 } }] }' ``` ### Success response ```json { "jobId": "clxxxxxxxx", "itemCount": 1, "status": "PENDING", "playlist": null } ``` ### 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 overlay - `default` — built-in Songs2VID badge PNG (`assets/watermark.png`) - `logo` — requires prior `type=logo` upload; set `logoPath` - `text` — custom string (max **80**); set `text` `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**: ```json { "error": "Invalid layout template. Refer to API documentation for valid enum values." } ``` ## YouTube playlists ```bash curl "$BASE_URL/api/v1/playlists" \ -H "Authorization: Bearer $API_KEY" ``` ```bash 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) ```bash 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 ```bash curl "$BASE_URL/api/v1/jobs/JOB_ID" \ -H "Authorization: Bearer $API_KEY" ``` ```bash 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.