The Valiha API

On this page

The API lets your own app do what the studio does. Your server sends a recording and its lyrics. Valiha answers at once, analyses the song in a few minutes, then gives you the key, the tempo, the sections, the chords on every beat, a ChordPro chart, the separated parts (stems) and the sung notes.

Base URL:

https://valiha.marindrano.com/v1

Requests and answers are JSON, except the recording upload (multipart), the ChordPro text and the stems (audio). All times are in seconds. Dates are ISO 8601 in UTC.

Keys and plans

The API is part of the Plus plan and of partner plans. A key of an organization on the Free plan is refused with a 401 and the message The API is available on Plus and Partner plans.

To create a key:

  1. Sign in to the studio and switch to the organization the songs belong to (in the account menu).
  2. Open API in the sidebar, under Organization. Only owners and admins see it.
  3. Click Create key, give it a name after the app that will use it (for example StarApp production), then Create the key.
  4. Copy the API key and the Webhook secret and keep them on your server. Valiha keeps neither and shows them only this once. Click Done, I copied them.

A key looks like vk_live_ followed by 42 letters and digits. Create one key per app, so you can revoke one without stopping the others. Revoke stops a key at once; it stays in the list as revoked.

The API page also shows the active keys, the songs created through the API this month and the time of the last call. Songs created with a key belong to the key's organization: they appear in the studio's project list with a "via API" mark, and their song page shows the last webhook delivery.

Never put a key in a mobile app or a web page. Call the API from your server.

Authentication

Send the key in the Authorization header of every request:

Authorization: Bearer vk_live_...

Every request runs in the organization that owns the key. A song of another organization answers 404, as if it did not exist.

ProblemStatusMessage
No header, or not a Valiha key401Send your API key as Authorization: Bearer vk_live_...
Unknown key401This API key is not valid
Revoked key401This API key was revoked
Organization on the Free plan401The API is available on Plus and Partner plans

A 401 also carries WWW-Authenticate: Bearer realm="valiha".

Endpoints

MethodPathWhat it doesAnswers
POST/v1/songsCreate a song and start its analysis202, JSON
GET/v1/songs/{id}One song200, the song object
GET/v1/songs?externalId=...Your songs with this externalId, newest first200, a list of song objects
GET/v1/songs/{id}/chordproThe chart as ChordPro text200, text/plain
GET/v1/songs/{id}/stems/{name}One part of the current analysis200 or 206, audio/mp4
POST/v1/songs/{id}/analyzeAnalyze the song again, with new options if you send them202, JSON
DELETE/v1/songs/{id}Delete the song, its recording and its stems204

Create a song

POST /v1/songs takes the song and its recording in one request. Send the recording in one of two ways:

  • JSON with audioUrl: Valiha downloads the file from your link.
  • A multipart form with the file in a field named audio.

Fields

FieldRequiredRules
titleyes1 to 200 characters
artistnoup to 200 characters
languageyesthe language of the lyrics: mg (Malagasy), fr or en
lyricsyesup to 20,000 characters; may be "" in JSON
audioUrlJSON onlyan https:// link to the recording
audiomultipart onlythe recording, as a file field
webhookUrlnoan https:// address Valiha posts to when the analysis ends (see Webhooks)
externalIdnoyour own id for the song, up to 120 characters, to find it again
optionsnothe parts and the notes to make (see Parts and notes)

The lyrics are plain text, one sung line per line. A line such as [Verse 1] or [Chorus] names the section that follows, as in the studio (see Using the studio).

Shell
curl -X POST https://valiha.marindrano.com/v1/songs \
  -H "Authorization: Bearer $VALIHA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Antsoantso",
    "artist": "Rija",
    "language": "mg",
    "lyrics": "[Verse 1]\nTompo ô, Ianao no hirako\nNy fitiavanao no tanjako",
    "audioUrl": "https://example.com/antsoantso.mp3",
    "webhookUrl": "https://example.com/hooks/valiha",
    "externalId": "song-1042",
    "options": { "stems": 6, "melody": ["game", "choir"] }
  }'

Valiha fetches audioUrl from its own server before it answers. The link must:

  • start with https://, and stay on https if it redirects;
  • answer with a 2xx status within 60 seconds;
  • send an audio/* Content-Type (audio/mpeg, audio/mp4, audio/wav and so on; application/octet-stream is refused);
  • be at most 80 MB.

A signed link to your storage (S3, R2, Google Cloud Storage) works well, as long as the object has an audio Content-Type.

With the file

Shell
curl -X POST https://valiha.marindrano.com/v1/songs \
  -H "Authorization: Bearer $VALIHA_KEY" \
  -F "title=Antsoantso" \
  -F "language=mg" \
  -F "lyrics=<lyrics.txt" \
  -F "audio=@antsoantso.mp3" \
  -F "webhookUrl=https://example.com/hooks/valiha" \
  -F "externalId=song-1042" \
  -F 'options={"stems": 6, "melody": ["game", "choir"]}'
  • The file goes in the field audio, at most 80 MB.
  • Its type is the one the form gives it. When that is not an audio type (curl sends an .mp3 as application/octet-stream), Valiha takes it from the file name: .mp3, .m4a, .mp4, .aac, .wav, .flac, .ogg, .oga, .opus, .aif, .aiff, .webm.
  • options is one text field that holds the same JSON as in a JSON request.
  • An empty field counts as missing. Send at least one character of lyrics in a form.
  • audioUrl is not read from a form: send the file.

Parts and notes

options chooses what the analysis makes, as the fourth step of a new project in the studio does. Leave it out for 4 parts and the main melody.

JSON
{ "stems": 4, "melody": ["game"] }

stems is how many parts the recording is split into:

stemsParts you get
2vocals, music (everything but the voice)
4vocals, drums, bass, other (the default)
6vocals, drums, bass, guitar, piano, other

melody lists the note models that listen to the vocals part, each at most once:

ModelWhat it writesWhere you get it
gameThe main melody: the sung tune as notes on the beat gridmelody in the song object
choirEvery voice it hears, several notes at once. Experimentalvoices in the song object

[] writes no notes. Both fields are required inside options. Any other field in options is ignored.

What to expect from the engine:

  • With 6, the first song on an engine downloads the six part model (about 55 MB), so it takes longer.
  • When the engine does not have GAME, it writes the melody with its older method: melody.model is then classic.
  • When the engine does not have the Choir model, or a note model fails, the song is still ready: voices (or melody) is null.

The chords, the beats and the words come out the same with every choice.

The answer

The song is created and its analysis queued before Valiha answers:

JSON
HTTP/1.1 202 Accepted

{
  "id": "cm3k2x8f40001",
  "externalId": "song-1042",
  "status": "queued",
  "options": { "stems": 6, "melody": ["game", "choir"] },
  "createdAt": "2026-10-27T09:12:00.000Z"
}

Keep the id. A request that fails (a bad field, an audio link that does not answer) creates nothing.

Get the result

An analysis takes a few minutes. Ask for the song now and then, or give a webhookUrl and wait for Valiha's call. You can do both.

Ask for it

Shell
curl https://valiha.marindrano.com/v1/songs/cm3k2x8f40001 \
  -H "Authorization: Bearer $VALIHA_KEY"

Ask every 10 seconds or so until status is ready or failed:

statusMeaning
queuedWaiting for an engine
analyzingAn engine is working on it
readyDone: chart, stems, melody and voices are filled
failedIt did not work: error says why

When no engine is online, the song stays queued and Valiha keeps trying for about eight minutes, then the song fails with No engine is online.

Find songs by your id

Shell
curl "https://valiha.marindrano.com/v1/songs?externalId=song-1042" \
  -H "Authorization: Bearer $VALIHA_KEY"

The answer is a list of song objects with that externalId, newest first, at most 50. Without ?externalId= the answer is a 400.

The song object

Every endpoint and every webhook gives songs in this shape:

JSON
{
  "id": "cm3k2x8f40001",
  "externalId": "song-1042",
  "status": "ready",
  "title": "Antsoantso",
  "artist": "Rija",
  "language": "mg",
  "key": "G",
  "bpm": 91,
  "timeSignature": "4/4",
  "durationSeconds": 212.4,
  "error": null,
  "chart": {
    "chordpro": "{title: Antsoantso}\n{key: G}\n{tempo: 91}\n{time: 4/4}\n...",
    "sections": [
      { "kind": "verse", "label": "Verse 1", "start": 8.2, "end": 41.6 }
    ],
    "chords": [
      { "time": 8.21, "beat": 12, "name": "G" },
      { "time": 10.84, "beat": 16, "name": "D/F#" }
    ]
  },
  "options": { "stems": 6, "melody": ["game", "choir"] },
  "stems": ["vocals", "drums", "bass", "guitar", "piano", "other"],
  "melody": {
    "version": 1,
    "model": "game",
    "engine": { "version": "0.1.0" },
    "sha1": "9b1c4e...",
    "tuning": -12.5,
    "notes": [
      { "start": 8.31, "end": 8.74, "midi": 67, "beat": 12, "length": 1, "conf": 1, "syllable": [0, 0, 0, 0] }
    ],
    "timings": { "notes": 0.41 }
  },
  "voices": {
    "version": 1,
    "model": "choir",
    "sha1": "9b1c4e...",
    "tuning": -12.5,
    "notes": [
      { "start": 8.3, "end": 8.76, "midi": 67, "velocity": 0.81 },
      { "start": 8.3, "end": 8.76, "midi": 59, "velocity": 0.44 }
    ],
    "timings": { "features": 3.2, "model": 18.7 }
  },
  "createdAt": "2026-10-27T09:12:00.000Z",
  "updatedAt": "2026-10-27T09:15:32.000Z"
}
FieldTypeMeaning
idstringValiha's id of the song
externalIdstring or nullYour id, as you sent it
statusstringqueued, analyzing, ready or failed
title, artist, languagestringAs you sent them (artist may be null)
keystring or nullThe key, such as G or Em
bpmnumber or nullThe tempo in beats per minute
timeSignaturestring or nullSuch as 4/4 or 3/4
durationSecondsnumber or nullThe length of the recording
errorstring or nullWhy the analysis failed, only when status is failed
chartobject or nullThe chart, null until the first analysis is ready
chart.chordprostringThe chart as ChordPro text, the same as the /chordpro endpoint
chart.sectionslistEach section: kind (verse, chorus, bridge, intro, outro, instrumental, other), label ("" when none), start and end in seconds
chart.chordslistEach chord change: time in seconds, beat (its index in the song's beats) and name
optionsobjectWhat the song's analysis is asked to make: the options you sent last, else {"stems": 4, "melody": ["game"]}
stemslistThe parts of the current analysis you can download, [] when there are none
melodyobject or nullThe main melody (melody.json), null when the analysis has none
voicesobject or nullEvery voice (voices.json), null when the analysis has none
createdAt, updatedAtstringISO 8601 dates

Good to know:

  • chart.sections and chart.chords are read from the engine's analysis with the default chart settings: letter names, a chord at each change, slash chords. chart.chordpro is the chart as it is now, so it also holds the fixes and the transposition someone made in the studio.
  • During a new analysis (status queued or analyzing again), chart, stems, melody and voices still show the previous result until the new one is ready. options already shows the new choice.
  • The full description of melody, voices, the stems and the ChordPro text is in Data formats.

The ChordPro text

Shell
curl https://valiha.marindrano.com/v1/songs/cm3k2x8f40001/chordpro \
  -H "Authorization: Bearer $VALIHA_KEY" \
  -o antsoantso.cho

The answer is text/plain; charset=utf-8. Before the first analysis is ready it is a 404 with The chart is not ready yet. A chart edited in the studio can start with {x_valiha_chart: ...} and {x_valiha_fixes: ...} lines: they keep the studio's settings, and ChordPro readers skip them. See Studio directives.

The stems

Shell
curl https://valiha.marindrano.com/v1/songs/cm3k2x8f40001/stems/vocals \
  -H "Authorization: Bearer $VALIHA_KEY" \
  -o vocals.m4a

{name} is one of the names in the song's stems list: vocals, drums, bass, guitar, piano, other or music. Each part is AAC audio in an .m4a file, sent as audio/mp4.

  • Range requests work (206 Partial Content), so a player can stream a part.
  • Each answer has an ETag; send it back in If-None-Match to get a 304 when nothing changed.
  • X-Valiha-Version names the analysis run the stems come from. It changes when a new analysis brings new stems.
  • A name that is not a part, or a part the current analysis does not have, is a 404.

Analyze again

POST /v1/songs/{id}/analyze runs the analysis again on the same recording. An analysis still running is cancelled first.

Without a body, the song keeps its options. With a JSON body, new options become the song's:

Shell
curl -X POST https://valiha.marindrano.com/v1/songs/cm3k2x8f40001/analyze \
  -H "Authorization: Bearer $VALIHA_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "options": { "stems": 2, "melody": ["game"] } }'

The answer has the same shape as song creation, with the options the new analysis runs with:

JSON
HTTP/1.1 202 Accepted

{
  "id": "cm3k2x8f40001",
  "externalId": "song-1042",
  "status": "queued",
  "options": { "stems": 2, "melody": ["game"] },
  "createdAt": "2026-10-27T09:12:00.000Z"
}

The song's webhook, if it has one, is called again when this analysis ends.

Delete a song

Shell
curl -X DELETE https://valiha.marindrano.com/v1/songs/cm3k2x8f40001 \
  -H "Authorization: Bearer $VALIHA_KEY"

The answer is a 204 with no body. The song, its recording and the stems of every analysis are deleted, and an analysis still running is stopped. This cannot be undone.

Webhooks

When a song has a webhookUrl, Valiha posts the song to it each time an analysis ends:

POST https://example.com/hooks/valiha
Content-Type: application/json
User-Agent: Valiha-Webhook/1
X-Valiha-Event: song.ready
X-Valiha-Timestamp: 1793092532
X-Valiha-Signature: sha256=5f0c9a...

{"event": "song.ready", "song": { ...the song object... }}
EventWhen
song.readyThe analysis is done. song.chart, song.stems, song.melody and song.voices are filled
song.failedThe analysis failed, or no engine could take it. song.error says why

Delivery

  • Answer with any 2xx status within 10 seconds. Do the slow work after you answer.
  • A redirect is not followed and counts as a failure, like a timeout or any other status.
  • After a failure Valiha tries again 30 seconds later, then 5 minutes after that, then stops: three tries in all.
  • The same event can reach you twice (for example when your answer arrived after the 10 seconds). Use the song's id and updatedAt to skip one you already handled.
  • Polling GET /v1/songs/{id} always works, so a missed webhook is never a lost result.

Check the signature

Each key comes with a webhook secret, shown once next to the key. Every webhook of a song created with that key is signed with it:

  1. Take X-Valiha-Timestamp (Unix seconds) and the raw body, exactly the bytes you received, before any JSON parsing.
  2. Compute the HMAC SHA-256 of <timestamp>.<raw body>, with the secret as the key. Use the secret as text, as you copied it: do not decode it from hex.
  3. Write it as sha256= followed by the lowercase hex digest, and compare it with X-Valiha-Signature in constant time.
  4. Refuse a timestamp more than five minutes away from your clock, so an old request cannot be replayed.

JavaScript (Node):

JavaScript
import { createHmac, timingSafeEqual } from "node:crypto";

export function isFromValiha(rawBody, headers, webhookSecret) {
  const timestamp = headers["x-valiha-timestamp"];
  const signature = headers["x-valiha-signature"] ?? "";
  const age = Math.abs(Date.now() / 1000 - Number(timestamp));
  if (!timestamp || !(age < 300)) return false;

  const expected =
    "sha256=" +
    createHmac("sha256", webhookSecret)
      .update(`${timestamp}.${rawBody}`)
      .digest("hex");

  const a = Buffer.from(signature);
  const b = Buffer.from(expected);
  return a.length === b.length && timingSafeEqual(a, b);
}

Python:

Python
import hashlib
import hmac
import time


def is_from_valiha(raw_body: bytes, headers, webhook_secret: str) -> bool:
    timestamp = headers.get("X-Valiha-Timestamp", "")
    signature = headers.get("X-Valiha-Signature", "")
    try:
        age = abs(time.time() - int(timestamp))
    except ValueError:
        return False
    if age >= 300:
        return False
    digest = hmac.new(
        webhook_secret.encode(),
        timestamp.encode() + b"." + raw_body,
        hashlib.sha256,
    ).hexdigest()
    return hmac.compare_digest(signature, "sha256=" + digest)

Errors

Errors come back as JSON, with a code for your program and a message for people:

JSON
HTTP/1.1 404 Not Found

{ "error": { "code": "not_found", "message": "This song does not exist" } }
CodeStatusMeaning
invalid400A field is missing or wrong. The message names it, such as options.stems: Invalid input
unauthorized401The key is missing, wrong or revoked, or the plan has no API
not_found404No such song in your organization, no chart yet, or no such stem
limit429Too many requests. Wait the seconds in Retry-After
server500Something failed on Valiha's side. Try again a little later

Messages you may meet:

MessageWhat to do
Send application/json with audioUrl, or multipart/form-data with an audio fileSet the Content-Type of the request
audioUrl is requiredA JSON request needs audioUrl
Send the audio file in the field audioA form needs a file field named audio
The audio URL answered 403Valiha could not download your link. Check that it is public or signed
The audio URL must serve an audio/* content typeSet the Content-Type of the stored file
The audio file is larger than 80 MBSend a smaller file (an mp3 at 192 kbps is far below)
This song has no audio to analyseThe song has no recording; create it again
The chart is not ready yetWait for status to be ready
This song has no such stem yetCheck the song's stems list

An audio storage outage answers a 503 with Retry-After: 10 on the stem downloads. Try again after the delay.

Limits

WhatLimit
Requests60 a minute per key. The count is kept per server, so treat 60 as a ceiling
Recording80 MB, by link or by file
Downloading audioUrl60 seconds
Lyrics20,000 characters
Title, artist200 characters each
externalId120 characters
Songs listed by externalIdthe 50 newest
Webhook answer10 seconds, three tries

Complete examples

Each example creates a song from a file, waits for the analysis, and saves the ChordPro chart and every stem. Set VALIHA_KEY in your environment first.

curl

This script needs curl and jq.

Shell
#!/usr/bin/env bash
set -euo pipefail

API="https://valiha.marindrano.com/v1"
AUTH="Authorization: Bearer $VALIHA_KEY"

# 1. Create the song from a file on disk
id=$(curl -sS -X POST "$API/songs" -H "$AUTH" \
  -F "title=Antsoantso" \
  -F "language=mg" \
  -F "lyrics=<lyrics.txt" \
  -F "audio=@antsoantso.mp3" \
  -F 'options={"stems": 4, "melody": ["game"]}' | jq -r .id)
echo "Created $id"

# 2. Wait for the analysis
while :; do
  status=$(curl -sS "$API/songs/$id" -H "$AUTH" | jq -r .status)
  echo "Status: $status"
  case "$status" in ready | failed) break ;; esac
  sleep 10
done

# 3. Save the song, the chart and the stems
curl -sS "$API/songs/$id" -H "$AUTH" -o song.json
if [ "$status" = failed ]; then
  jq -r .error song.json
  exit 1
fi
curl -sS "$API/songs/$id/chordpro" -H "$AUTH" -o antsoantso.cho
for stem in $(jq -r '.stems[]' song.json); do
  curl -sS "$API/songs/$id/stems/$stem" -H "$AUTH" -o "$stem.m4a"
done

JavaScript

Node 20 or later, no packages: fetch, FormData and fs.openAsBlob are built in.

JavaScript
import { openAsBlob } from "node:fs";
import { writeFile } from "node:fs/promises";

const API = "https://valiha.marindrano.com/v1";
const headers = { Authorization: `Bearer ${process.env.VALIHA_KEY}` };

async function call(path, init = {}) {
  const response = await fetch(`${API}${path}`, {
    ...init,
    headers: { ...headers, ...init.headers },
  });
  if (!response.ok) {
    const body = await response.json().catch(() => null);
    throw new Error(body?.error?.message ?? `HTTP ${response.status}`);
  }
  return response;
}

// 1. Create the song from a file on disk
const form = new FormData();
form.set("title", "Antsoantso");
form.set("language", "mg");
form.set("lyrics", "[Verse 1]\nTompo ô, Ianao no hirako\nNy fitiavanao no tanjako");
form.set("externalId", "song-1042");
form.set("options", JSON.stringify({ stems: 4, melody: ["game"] }));
form.set(
  "audio",
  await openAsBlob("antsoantso.mp3", { type: "audio/mpeg" }),
  "antsoantso.mp3",
);
const created = await (await call("/songs", { method: "POST", body: form })).json();
console.log("Created", created.id);

// 2. Wait for the analysis
let song;
for (;;) {
  song = await (await call(`/songs/${created.id}`)).json();
  if (song.status === "ready" || song.status === "failed") break;
  await new Promise((resolve) => setTimeout(resolve, 10_000));
}
if (song.status === "failed") throw new Error(song.error);

// 3. Save the chart and the stems
console.log(song.key, song.bpm, song.chart.chords.length, "chord changes");
await writeFile("antsoantso.cho", song.chart.chordpro);
for (const name of song.stems) {
  const audio = await call(`/songs/${song.id}/stems/${name}`);
  await writeFile(`${name}.m4a`, Buffer.from(await audio.arrayBuffer()));
}

With a link instead of a file, send JSON:

JavaScript
await call("/songs", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    title: "Antsoantso",
    language: "mg",
    lyrics: "",
    audioUrl: "https://example.com/antsoantso.mp3",
    webhookUrl: "https://example.com/hooks/valiha",
    options: { stems: 2, melody: [] },
  }),
});

A webhook receiver with node:http, using isFromValiha from Check the signature:

JavaScript
import { createServer } from "node:http";

createServer(async (request, response) => {
  const chunks = [];
  for await (const chunk of request) chunks.push(chunk);
  const rawBody = Buffer.concat(chunks).toString("utf8");

  if (!isFromValiha(rawBody, request.headers, process.env.VALIHA_WEBHOOK_SECRET)) {
    response.writeHead(401).end();
    return;
  }
  response.writeHead(204).end();

  const { event, song } = JSON.parse(rawBody);
  console.log(event, song.id, song.status);
}).listen(3000);

Python

With requests (pip install requests). httpx takes the same arguments.

Python
import json
import os
import time

import requests

API = "https://valiha.marindrano.com/v1"
session = requests.Session()
session.headers["Authorization"] = f"Bearer {os.environ['VALIHA_KEY']}"


def check(response: requests.Response) -> requests.Response:
    if not response.ok:
        try:
            message = response.json()["error"]["message"]
        except ValueError:
            message = f"HTTP {response.status_code}"
        raise RuntimeError(message)
    return response


# 1. Create the song from a file on disk
with open("lyrics.txt", encoding="utf-8") as f:
    lyrics = f.read()
with open("antsoantso.mp3", "rb") as audio:
    created = check(
        session.post(
            f"{API}/songs",
            data={
                "title": "Antsoantso",
                "language": "mg",
                "lyrics": lyrics,
                "externalId": "song-1042",
                "options": json.dumps({"stems": 4, "melody": ["game"]}),
            },
            files={"audio": ("antsoantso.mp3", audio, "audio/mpeg")},
            timeout=300,
        )
    ).json()
print("Created", created["id"])

# 2. Wait for the analysis
while True:
    song = check(session.get(f"{API}/songs/{created['id']}", timeout=30)).json()
    if song["status"] in ("ready", "failed"):
        break
    time.sleep(10)
if song["status"] == "failed":
    raise RuntimeError(song["error"])

# 3. Save the chart and the stems
print(song["key"], song["bpm"], len(song["chart"]["chords"]), "chord changes")
with open("antsoantso.cho", "w", encoding="utf-8") as f:
    f.write(song["chart"]["chordpro"])
for name in song["stems"]:
    stem = check(session.get(f"{API}/songs/{song['id']}/stems/{name}", timeout=120))
    with open(f"{name}.m4a", "wb") as f:
        f.write(stem.content)

With a link instead of a file, send JSON:

Python
check(
    session.post(
        f"{API}/songs",
        json={
            "title": "Antsoantso",
            "language": "mg",
            "lyrics": "",
            "audioUrl": "https://example.com/antsoantso.mp3",
            "webhookUrl": "https://example.com/hooks/valiha",
            "options": {"stems": 2, "melody": []},
        },
        timeout=120,
    )
)

A webhook receiver with Flask, using is_from_valiha from Check the signature:

Python
import os

from flask import Flask, request

app = Flask(__name__)


@app.post("/hooks/valiha")
def valiha_webhook():
    raw_body = request.get_data()
    if not is_from_valiha(raw_body, request.headers, os.environ["VALIHA_WEBHOOK_SECRET"]):
        return "", 401
    payload = request.get_json()
    print(payload["event"], payload["song"]["id"], payload["song"]["status"])
    return "", 204