API:et från DinLåt tar fram personliga låtar programmatiskt: in går ett tillfälle och några detaljer, ut kommer en färdig text och en producerad inspelning. Enbart HTTPS och JSON, inget obligatoriskt SDK.
Uppdaterad: 2026-09-16
Nycklar delas ut för hand. Ett kort mejl med ditt upplägg, förväntad volym och språk räcker, och frisläppningen tar normalt en arbetsdag.
API:et gör exakt det som webbplatsen gör. Du skickar in ett tillfälle, namnet på den som låten handlar om och några konkreta detaljer. Ur det växer först en färdig text, sedan en producerad inspelning med sång, arrangemang och mix. Hela vägen tar normalt fem till tio minuter.
Alla anrop går till https://api.dinlat.se/v1. API:et talar bara HTTPS, tar emot JSON och svarar med JSON. Det finns inget obligatoriskt SDK: vilket språk som helst som klarar HTTP duger. Exemplen på den här sidan använder curl, Python och Node, eftersom det är de tre vanligaste fallen.
Varje domän har sin egen API-bas och sitt eget pris i lokal valuta. En nyckel gäller för den domän den utfärdades för. Den som betjänar flera marknader får flera nycklar eller en nyckel som är öppnad för flera domäner.
Debitering sker per färdig låt, för närvarande 299 SEK. Utkast, avbrutna beställningar och omgenereringar kostar ingenting.
Det finns medvetet ingen automatisk registrering. Vi delar ut nycklar för hand, eftersom varje låt innebär verkliga produktionskostnader och vi vill veta vad integrationen ska användas till. I praktiken handlar det om ett kort mejl och en arbetsdag.
Skriv till songs@maxkuch.com och nämn fyra saker:
Du får två nycklar: en testnyckel med prefixet sk_test_, gratis, som lämnar fasta demoinspelningar, och en livenyckel med prefixet sk_live_. Båda fungerar direkt, utan att enskilda slutpunkter behöver låsas upp.
Varje anrop bär nyckeln i Authorization-huvudet som bearer-token. Anrop utan giltigt huvud får 401 och feltypen authentication_error.
curl https://api.dinlat.se/v1/songs \
-H "Authorization: Bearer $SONG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"occasion": "birthday",
"recipient_name": "Anna",
"relationship": "sister",
"language": "sv",
"mood": "happy",
"style": "pop",
"voice": "female",
"details": "Climbs every weekend, always ten minutes late, calls everyone chef.",
"callback_url": "https://example.com/hooks/songs"
}'
Behandla nyckeln som ett lösenord: bara på serversidan, aldrig i frontend-kod, aldrig i ett öppet repository. Om en nyckel kommer på avvägar, skriv till oss: vi spärrar den direkt och utfärdar en ny. Ett konto kan ha flera aktiva nycklar, så bytet sker utan avbrott.
Test- och livenycklar delar samma slutpunkter. Om ett anrop skedde i testläge framgår av fältet livemode i varje objekt.
Att skapa en låt är ett enda anrop. Svaret kommer direkt och innehåller ett id med status queued. Allt annat sker i bakgrunden.
import os, time, requests
API = "https://api.dinlat.se/v1"
HEAD = {"Authorization": "Bearer " + os.environ["SONG_API_KEY"]}
song = requests.post(API + "/songs", headers=HEAD, json={
"occasion": "wedding",
"recipient_name": "Lea and Tim",
"relationship": "friends",
"language": "sv",
"mood": "romantic",
"details": "Met at a bike repair shop, dog named Miso, both terrible dancers.",
}).json()
while song["status"] not in ("preview_ready", "complete", "failed"):
time.sleep(5)
song = requests.get(API + "/songs/" + song["id"], headers=HEAD).json()
print(song["lyrics"])
print(song["preview_url"])
Exemplet pollar var femte sekund för enkelhetens skull. I produktion är webhooks den bättre vägen, eftersom de sparar både öppen uppkoppling och väntan. Båda stöds, webhooks beskrivs längre ner.
Det avgörande fältet är details. Där hör det konkreta om personen hemma: smeknamnet, maniskheten, semestern som gick åt skogen. Allmänna meningar som "hon är en varm människa" ger allmänna rader. Tre till fem konkreta detaljer räcker, och de är skillnaden mellan en trevlig låt och en som verkligen handlar om någon.
| Metod | Sökväg | Syfte |
|---|---|---|
| POST | /v1/songs | Beställa en ny låt. |
| GET | /v1/songs/{id} | Hämta en låt med alla aktuella fält. |
| GET | /v1/songs | Lista kontots låtar, med filter och sidindelning. |
| GET | /v1/songs/{id}/lyrics | Hämta enbart texten som ren text. |
| GET | /v1/songs/{id}/audio | Signerad nedladdningslänk för förhandslyssning eller hela inspelningen. |
| POST | /v1/songs/{id}/regenerate | Starta en kostnadsfri omgenerering. |
| POST | /v1/songs/{id}/checkout | Skapa en betalsida för slutkunden. |
| POST | /v1/songs/{id}/unlock | Låsa upp låten direkt och debitera kontot. |
| GET | /v1/options | Alla giltiga värden för tillfälle, stämning, stil, röst och språk. |
| GET | /v1/account | Saldo, gränser och öppnade domäner. |
| DELETE | /v1/songs/{id} | Avbryta en låt som ännu inte är klar. |
POST /v1/songs tar emot briefen och startar direkt. Bara tre fält är obligatoriska, alla andra har rimliga standardvärden eller väljs utifrån tillfället.
| Fält | Typ | Beskrivning |
|---|---|---|
| string | obligatoriskt | Tillfället. Giltiga värden kommer från /v1/options. |
| string | obligatoriskt | Namnet på den som låten handlar om. Används i texten. |
| string | obligatoriskt | Konkreta detaljer om personen, 40 till 4000 tecken. Det här fältet avgör kvaliteten. |
| string | valfritt | Relationen mellan beställare och mottagare, till exempel syster, kollega, partner. |
| string | valfritt | Språket som sjungs. Standardvärdet är sv. |
| string | valfritt | Grundstämning. Utan uppgift väljer vi en som passar tillfället. |
| string | valfritt | Musikstil. Utan uppgift väljer vi en som passar tillfälle och stämning. |
| string | valfritt | Sångröst. Utan uppgift väljer vi en som passar tillfället. |
| string | valfritt | Ett budskap som ska förekomma i låten. |
| string | valfritt | Fritext för allt som inte får plats någon annanstans, till exempel önskemål om tempo. |
| string | valfritt | HTTPS-adress dit händelser ska skickas. |
| object | valfritt | Fria nyckel-värde-par, högst 20. Kommer tillbaka oförändrade. |
const res = await fetch("https://api.dinlat.se/v1/songs", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.SONG_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify({
occasion: "anniversary",
recipient_name: "Mara",
relationship: "partner",
language: "sv",
mood: "warm",
details: "Ten years, three apartments, one very loud coffee machine.",
callback_url: "https://example.com/hooks/songs",
metadata: { order_id: "A-10423" },
}),
});
const song = await res.json();
console.log(song.id, song.status);
Anropet kostar ingenting. Betalning sker först när låten låses upp via /unlock eller en genomförd betalsession.
Varje slutpunkt som lämnar en enskild låt lämnar samma objekt. Fält som ännu inte finns är null och fylls i under produktionen.
{
"id": "sng_3n8Kd2ZpQv",
"object": "song",
"status": "queued",
"created_at": "2026-09-16T09:41:02Z",
"occasion": "birthday",
"recipient_name": "Anna",
"relationship": "sister",
"language": "sv",
"mood": "happy",
"style": "pop",
"voice": "female",
"lyrics": null,
"preview_url": null,
"audio_url": null,
"duration_seconds": null,
"paid": false,
"price": { "amount": 2999, "currency": "SEK" },
"metadata": {},
"livemode": true
}
| Fält | Typ | Beskrivning |
|---|---|---|
| string | valfritt | Unik identifierare, börjar alltid med sng_. |
| string | valfritt | Aktuellt produktionsläge, se nästa avsnitt. |
| string | valfritt | Den fullständiga texten med vers- och refrängmarkeringar. Gratis, även utan betalning. |
| string | valfritt | De första 45 sekunderna som MP3. Alltid tillgängliga, utan betalning. |
| string | valfritt | Hela inspelningen som MP3, signerad och giltig i 24 timmar. Fylls i först efter betalning. |
| integer | valfritt | Den färdiga inspelningens längd i sekunder, oftast mellan 120 och 240. |
| boolean | valfritt | Om låten är upplåst. |
| object | valfritt | Belopp i minsta valutaenhet plus valutakod, här 299 SEK. |
| object | valfritt | Det du skickade med vid skapandet, oförändrat. |
| boolean | valfritt | false om anropet gjordes med en testnyckel. |
{
"id": "sng_3n8Kd2ZpQv",
"object": "song",
"status": "complete",
"created_at": "2026-09-16T09:41:02Z",
"completed_at": "2026-09-16T09:47:35Z",
"lyrics": "[Verse 1]\nAnna, six in the morning, chalk on your hands ...",
"preview_url": "https://cdn.dinlat.se/preview/sng_3n8Kd2ZpQv.mp3",
"audio_url": "https://cdn.dinlat.se/full/sng_3n8Kd2ZpQv.mp3?expires=1789412855&sig=...",
"duration_seconds": 184,
"paid": true,
"price": { "amount": 2999, "currency": "SEK" },
"metadata": { "order_id": "A-10423" },
"livemode": true
}
En låt går igenom lägena i den här ordningen. Den går aldrig bakåt, och complete, failed och cancelled är slutlägen.
| Status | Värde | Betydelse |
|---|---|---|
| queued | Mottagen, väntar på en ledig produktionsplats. Normalt några sekunder. | |
| writing_lyrics | Texten skrivs. | |
| lyrics_ready | Texten är färdig och kan hämtas. Oftast efter en till två minuter. | |
| generating_audio | Sång, arrangemang och mix produceras. | |
| preview_ready | De första 45 sekunderna finns och hela filen är klar. | |
| complete | Betald och levererad i sin helhet. | |
| failed | Produktionen misslyckades slutgiltigt. Ingenting debiteras, fältet error anger orsaken. | |
| cancelled | Avbruten före färdigställandet. |
Ett enskilt misslyckat produktionsförsök leder inte direkt till failed. Internt försöker vi flera gånger och ger upp först när alla försök misslyckas. Därför är failed sällsynt och betyder verkligen: den här låten kommer inte.
GET /v1/songs/{id} lämnar det aktuella läget för en låt. Slutpunkten är lätt och tål att frågas varje sekund, så länge du håller dig inom frekvensgränsen.
curl -G https://api.dinlat.se/v1/songs \
-H "Authorization: Bearer $SONG_API_KEY" \
-d status=complete \
-d limit=20 \
-d starting_after=sng_3n8Kd2ZpQv
Listor är markörbaserade. Du får högst limit poster, 20 som standard och 100 som mest, med de nyaste först. Är has_more sant skickar du next_cursor som starting_after i nästa anrop. Du kan filtrera på status, occasion, language, paid samt created_after och created_before.
{
"object": "list",
"data": [
{ "id": "sng_9Wq1LmT4bR", "status": "complete", "recipient_name": "Jonas", "...": "..." },
{ "id": "sng_3n8Kd2ZpQv", "status": "complete", "recipient_name": "Anna", "...": "..." }
],
"has_more": true,
"next_cursor": "sng_3n8Kd2ZpQv"
}
Texten är gratis och fullständig, inte ett utdrag. GET /v1/songs/{id}/lyrics lämnar den som text/plain, med vers- och refrängmarkeringar. Samma text står i fältet lyrics i låtobjektet.
För ljudet finns två nivåer. Förhandslyssningen är de första 45 sekunderna av den färdiga inspelningen, inte en separat demo: samma röst, samma arrangemang, samma text. Den finns utan betalning och förblir tillgänglig. Hela filen lämnar GET /v1/songs/{id}/audio först efter upplåsning.
Båda adresserna är signerade och giltiga i 24 timmar. De är till för nedladdning, inte för permanent länkning. Behöver du en fil längre, ladda ner den en gång och lagra den själv. Ett nytt anrop mot slutpunkten ger när som helst en färsk adress.
Formatet är alltid MP3 med 320 kbit/s. Den som behöver WAV lägger till ?format=wav, tillgängligt för konton med studioalternativet.
Om ett resultat inte övertygar kostar en omgenerering ingenting. POST /v1/songs/{id}/regenerate skapar en ny version under samma id och sätter status tillbaka till queued. Den tidigare versionen ligger kvar under previous_versions.
curl https://api.dinlat.se/v1/songs/sng_3n8Kd2ZpQv/regenerate \
-H "Authorization: Bearer $SONG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"keep_lyrics": false,
"reason": "voice_not_matching",
"note": "Please try a lower male voice and a slower tempo."
}'
Med keep_lyrics: true står texten kvar och bara inspelningen görs om. Det är rätt väg när texten sitter och bara rösten eller tempot var fel. Med false skrivs även texten om.
Fältet note går rakt in i omgenereringen, så en konkret mening lönar sig. "Djupare manlig röst, långsammare" fungerar, "gör den bättre" gör det inte. Tre omgenereringar per låt är gratis, därutöver hör av dig.
Det finns två sätt att låsa upp en låt, beroende på vem som betalar.
POST /v1/songs/{id}/checkout skapar en betalsida hos oss i domänens valuta, med de betalsätt som är vanliga i det landet. Du skickar kunden dit och får händelsen song.paid när betalningen går igenom.
curl https://api.dinlat.se/v1/songs/sng_3n8Kd2ZpQv/checkout \
-H "Authorization: Bearer $SONG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"success_url": "https://example.com/thanks?song=sng_3n8Kd2ZpQv",
"cancel_url": "https://example.com/cart"
}'
{
"object": "checkout_session",
"song": "sng_3n8Kd2ZpQv",
"url": "https://pay.dinlat.se/c/cs_live_8Hd2Kq...",
"amount": 2999,
"currency": "SEK",
"expires_at": "2026-09-16T11:41:02Z"
}
På konton med samlingsfakturering låser POST /v1/songs/{id}/unlock upp låten direkt och debiterar 299 SEK på kontot. Ingen omväg via en betalsida, praktiskt när du har en egen kassa.
curl https://api.dinlat.se/v1/songs/sng_3n8Kd2ZpQv/unlock \
-H "Authorization: Bearer $SONG_API_KEY" \
-X POST
I båda fallen gäller samma nyttjanderätt: icke-exklusiv, men uttryckligen kommersiell. Du får lämna vidare, sälja och publicera den färdiga låten inom ramen för ditt erbjudande.
Listorna över tillfälle, stämning, stil, röst och språk ändras då och då. Skriv dem inte i koden, fråga GET /v1/options och cacha svaret i några timmar.
{
"object": "options",
"language": "sv",
"occasions": ["birthday", "wedding", "anniversary", "farewell", "funeral",
"christening", "graduation", "christmas", "declaration", "other"],
"moods": ["happy", "warm", "funny", "romantic", "gentle", "epic", "surprise_me"],
"styles": ["pop", "rock", "folk", "schlager", "hiphop", "ballad", "country",
"electronic", "jazz", "childrens", "surprise_me"],
"voices": ["female", "male", "duet", "choir", "childrens", "surprise_me"],
"languages": ["de", "en", "dk", "nl", "it", "se", "fr", "es", "no", "pl", "fi", "is", "jp"]
}
Vart och ett av dessa värden får också utelämnas. Värdet surprise_me är ingen platshållare utan en riktig instruktion: då väljer vi medvetet något som passar tillfället och detaljerna.
Ange en callback_url vid skapandet så skickar vi varje händelse dit med en POST. Det är den rekommenderade vägen eftersom den sparar pollning och väntan.
| Händelse | Typ | Utlöses när |
|---|---|---|
| song.lyrics_ready | Texten är färdig. | |
| song.preview_ready | Förhandslyssningen på 45 sekunder finns. | |
| song.completed | Hela inspelningen är levererad. | |
| song.failed | Produktionen misslyckades slutgiltigt. | |
| song.regenerated | En omgenerering är klar. | |
| song.paid | Betalningen har kommit in, låten är upplåst. |
{
"id": "evt_5Tb7Rn2WqX",
"object": "event",
"type": "song.completed",
"created_at": "2026-09-16T09:47:35Z",
"data": {
"object": {
"id": "sng_3n8Kd2ZpQv",
"object": "song",
"status": "complete",
"audio_url": "https://cdn.dinlat.se/full/sng_3n8Kd2ZpQv.mp3?expires=1789412855&sig=...",
"...": "..."
}
}
}
Varje leverans bär ett huvud med tidsstämpel och HMAC-SHA256 över tidsstämpel, punkt och den råa kroppen. Kontrollera det innan du litar på innehållet och kasta allt som är äldre än fem minuter.
X-Song-Signature: t=1789412855,v1=7f2c1d9a4b6e8035c1f7a29d4e5b0c8371a6d2f94e8b3c07a15d9e2f6b4c8a01
import hashlib, hmac, os, time
from flask import Flask, request, abort
SECRET = os.environ["SONG_WEBHOOK_SECRET"].encode()
app = Flask(__name__)
@app.post("/hooks/songs")
def hook():
header = request.headers.get("X-Song-Signature", "")
parts = dict(p.split("=", 1) for p in header.split(",") if "=" in p)
timestamp, signature = parts.get("t", ""), parts.get("v1", "")
if abs(time.time() - int(timestamp or 0)) > 300:
abort(400) # older than five minutes, treat as replay
expected = hmac.new(
SECRET, (timestamp + "." + request.get_data(as_text=True)).encode(),
hashlib.sha256,
).hexdigest()
if not hmac.compare_digest(expected, signature):
abort(400)
event = request.get_json()
if event["type"] == "song.completed":
store(event["data"]["object"])
return "", 200
Vi förväntar oss ett 2xx-svar inom tio sekunder. Uteblir det försöker vi åtta gånger under 24 timmar med växande mellanrum. Leveranser kan alltså upprepas och i sällsynta fall komma i fel ordning: gör din slutpunkt idempotent och lita på created_at, inte på ankomsttiden.
Varje POST tar emot huvudet Idempotency-Key med ett godtyckligt unikt värde, oftast en UUID. Kommer samma nyckel tillbaka inom 24 timmar lämnar vi det ursprungliga svaret i stället för att skapa en andra låt.
curl https://api.dinlat.se/v1/songs \
-H "Authorization: Bearer $SONG_API_KEY" \
-H "Idempotency-Key: 9f1c7c2e-0a3b-4c8d-9e21-5f7a1b6c3d40" \
-H "Content-Type: application/json" \
-d '{ "occasion": "birthday", "recipient_name": "Anna", "language": "sv", "details": "..." }'
Det är precis det skydd som behövs mot nätverksfel: går ett svar förlorat och din kod upprepar anropet blir det ändå bara en låt. Skickar du samma nyckel med en annan kropp svarar vi 409 med feltypen conflict.
Fel kommer alltid i samma form, med maskinläsbara type och code, ett läsbart meddelande och där det är relevant det berörda fältet. Ta med request_id i varje supportfråga, så hittar vi anropet i loggarna.
{
"error": {
"type": "validation_error",
"code": "details_too_short",
"message": "details must contain at least 40 characters so the song has something to work with",
"param": "details",
"request_id": "req_2Lm9Xc4Kd1"
}
}
| Typ | HTTP | Betydelse |
|---|---|---|
| 400 | invalid_request | Anropet är formellt trasigt, till exempel ogiltig JSON eller ett okänt fält. |
| 401 | authentication_error | Nyckeln saknas, har gått ut eller är spärrad. |
| 403 | permission_error | Nyckeln är giltig men inte öppnad för den här domänen eller slutpunkten. |
| 404 | not_found | Den efterfrågade identifieraren hör inte till det här kontot eller finns inte. |
| 409 | conflict | Åtgärden passar inte läget, till exempel att låsa upp en avbruten låt. |
| 422 | validation_error | Anropet är formellt korrekt men ett värde går inte att använda, till exempel för korta detaljer. |
| 429 | rate_limit | För många anrop eller för många samtidiga produktioner. |
| 500 | api_error | Fel på vår sida. Försök igen med växande mellanrum. |
Vid 429 och 5xx är det meningsfullt att försöka igen, helst med exponentiellt växande mellanrum och lite slump. Vid andra 4xx än 429 är det det inte: samma anrop misslyckas igen.
| Gräns | Värde | Gäller för |
|---|---|---|
| 60 / min | Anrop per minut och nyckel över alla slutpunkter. | |
| 10 | Samtidiga produktioner. Ytterligare anrop hamnar i kö. | |
| 64 KB | Största tillåtna storlek på en anropskropp. | |
| 40 - 4000 | Tecken i fältet details, minst och mest. | |
| 90 | Dagar som vi behåller låtar och indata, därefter raderas de. | |
| 24 h | Tid under vilken en idempotensnyckel lämnar det gamla svaret. |
Varje svar bär det aktuella läget i huvudena, så du slipper gissa.
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 57
X-RateLimit-Reset: 1789412880
X-Concurrent-Limit: 10
X-Concurrent-Running: 3
Högre gränser är inget problem, de är bara inte utgångsläget. Växer volymen, skriv två rader så höjer vi dem.
Huvudversionen står i sökvägen och ligger fast. Inom v1 kommer bara tillägg: nya fält, nya värden i listor, nya slutpunkter. Befintliga fält försvinner inte och byter inte betydelse.
För extra säkerhet kan du låsa ett datum i ett huvud. Utan huvud gäller alltid det senaste beteendet.
X-Song-Version: 2026-09-01
Din kod bör ignorera okända fält i svaren i stället för att gå sönder. Det är det enda antagande vi gör om klienter.
Nycklar med prefixet sk_test_ går genom exakt samma slutpunkter, men startar ingen riktig produktion och kostar ingenting. Efter några sekunder får du en fast demotext och en demoinspelning, och varje objekt bär livemode: false.
Så går också de obekväma fallen att prova. Vissa namn i fältet recipient_name tvingar fram ett bestämt utfall: test_fail leder till failed, test_slow till en produktion på ungefär tio minuter, test_ratelimit till ett 429-svar. Du kan alltså testa din felhantering utan att vänta på ett verkligt avbrott.
Webhooks fungerar också i testläge, med samma signaturmekanism och en egen hemlighet.
Med upplåsningen får du en icke-exklusiv men uttryckligen kommersiell nyttjanderätt till den färdiga låten. Du får lämna den vidare, sälja den, framföra den offentligt och bygga in den i din produkt. Icke-exklusiv betyder att vi behåller rätten att själva använda inspelningen, till exempel som exempel.
När det gäller upphovsrätt till musik som skapats med artificiell intelligens har många rättsordningar ännu inget slutgiltigt svar. Vi ger dig nyttjanderätten avtalsmässigt, men kan inte garantera att det uppstår en egen upphovsrätt till inspelningen som håller mot tredje part. Den som är beroende av det bör låta pröva saken i förväg.
Vi behåller indata och färdiga låtar i 90 dagar, sedan raderas de. För att radera en enskild låt tidigare finns DELETE /v1/songs/{id}. Uppgifterna i details använder vi bara för att producera just den låten och aldrig för att träna egna modeller.
Skickar du dina kunders uppgifter till oss är du personuppgiftsansvarig och vi personuppgiftsbiträde. Ett biträdesavtal finns på begäran.
Frågor, högre gränser, biträdesavtal, särskilda fall: songs@maxkuch.com. Vid tekniska problem, ange request_id från felsvaret så hittar vi anropet direkt.
För integrationer via AI-agenter finns dessutom en Model Context Protocol-server, dokumenterad på /mcp/, som använder samma nycklar som REST-API:et.
Nycklar delas ut för hand. Ett kort mejl med ditt upplägg, förväntad volym och språk räcker, och frisläppningen tar normalt en arbetsdag.