La API de CanciónAlMedida produce canciones personalizadas por programa: entra una ocasión y unos cuantos detalles, sale una letra terminada y una grabación producida. Solo HTTPS y JSON, sin SDK obligatorio.
Actualizado: 2026-09-16
Las claves se entregan a mano. Basta un correo breve con tu proyecto, el volumen previsto y los idiomas, y la activación suele tardar un día laborable.
La API hace exactamente lo mismo que la web. Envías una ocasión, el nombre de la persona de la que trata la canción y unos cuantos detalles concretos. De ahí nace primero una letra completa y después una grabación producida con voz, arreglo y mezcla. Todo el proceso suele durar entre cinco y diez minutos.
Todas las peticiones van a https://api.cancionalmedida.es/v1. La API solo habla HTTPS, acepta JSON y responde en JSON. No hay ningún SDK obligatorio: vale cualquier lenguaje que sepa hacer HTTP. Los ejemplos de esta página usan curl, Python y Node, porque son los tres casos más frecuentes.
Cada dominio tiene su propia base de API y su propio precio en la moneda local. Una clave vale para el dominio para el que se emitió. Quien atiende varios mercados recibe varias claves o una clave habilitada para varios dominios.
La facturación es por canción terminada, actualmente 29,99 €. Los borradores, los encargos cancelados y las regeneraciones no cuestan nada.
A propósito no existe un registro automático. Entregamos las claves a mano, porque detrás de cada canción hay costes de producción reales y queremos saber para qué sirve la integración. En la práctica son un correo breve y un día laborable.
Escribe a songs@maxkuch.com indicando cuatro cosas:
Recibirás dos claves: una de prueba con el prefijo sk_test_, gratuita, que devuelve grabaciones de demostración fijas, y una en vivo con el prefijo sk_live_. Ambas funcionan de inmediato, sin necesidad de habilitar endpoints uno a uno.
Cada petición lleva la clave en la cabecera Authorization como token bearer. Las peticiones sin cabecera válida reciben un 401 y el tipo de error authentication_error.
curl https://api.cancionalmedida.es/v1/songs \
-H "Authorization: Bearer $SONG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"occasion": "birthday",
"recipient_name": "Anna",
"relationship": "sister",
"language": "es",
"mood": "happy",
"style": "pop",
"voice": "female",
"details": "Climbs every weekend, always ten minutes late, calls everyone chef.",
"callback_url": "https://example.com/hooks/songs"
}'
Trata la clave como una contraseña: solo en el servidor, nunca en código de frontend, nunca en un repositorio público. Si una clave se pierde, escríbenos: la bloqueamos al momento y emitimos una nueva. Una cuenta puede tener varias claves activas, así que el relevo se hace sin interrupción.
Las claves de prueba y en vivo comparten los mismos endpoints. Si una petición ocurrió en modo de prueba lo indica el campo livemode de cada objeto.
Crear una canción es una sola llamada. La respuesta llega enseguida y contiene un identificador con el estado queued. Todo lo demás ocurre en segundo plano.
import os, time, requests
API = "https://api.cancionalmedida.es/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": "es",
"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"])
Por sencillez el ejemplo consulta cada cinco segundos. En producción los webhooks son el mejor camino, porque ahorran tanto la conexión abierta como la espera. Ambos se admiten; los webhooks se describen más abajo.
El campo decisivo es details. Ahí van las cosas concretas de la persona: el apodo, la manía, las vacaciones que salieron mal. Frases generales como "es una persona cariñosa" dan versos generales. Bastan tres a cinco detalles concretos, y son la diferencia entre una canción agradable y una canción que de verdad habla de alguien.
| Método | Ruta | Para qué sirve |
|---|---|---|
| POST | /v1/songs | Encargar una canción nueva. |
| GET | /v1/songs/{id} | Recuperar una canción con todos sus campos actuales. |
| GET | /v1/songs | Listar las canciones de la cuenta, con filtros y paginación. |
| GET | /v1/songs/{id}/lyrics | Recuperar solo la letra como texto plano. |
| GET | /v1/songs/{id}/audio | Enlace de descarga firmado para la vista previa o la grabación completa. |
| POST | /v1/songs/{id}/regenerate | Iniciar una regeneración gratuita. |
| POST | /v1/songs/{id}/checkout | Crear una página de pago para el cliente final. |
| POST | /v1/songs/{id}/unlock | Desbloquear la canción directamente y cargarla a la cuenta. |
| GET | /v1/options | Todos los valores válidos de ocasión, ambiente, estilo, voz e idioma. |
| GET | /v1/account | Saldo, límites y dominios habilitados. |
| DELETE | /v1/songs/{id} | Cancelar una canción que aún no está terminada. |
POST /v1/songs recibe el briefing y arranca de inmediato. Solo tres campos son obligatorios; los demás tienen valores por defecto razonables o se eligen según la ocasión.
| Campo | Tipo | Descripción |
|---|---|---|
| string | obligatorio | La ocasión. Los valores válidos vienen de /v1/options. |
| string | obligatorio | Nombre de la persona de la que trata la canción. Se usa en la letra. |
| string | obligatorio | Detalles concretos sobre la persona, de 40 a 4000 caracteres. Este campo decide la calidad. |
| string | opcional | La relación entre quien encarga y quien recibe, por ejemplo hermana, compañero, pareja. |
| string | opcional | El idioma en que se canta. El valor por defecto es es. |
| string | opcional | Ambiente general. Sin indicación elegimos uno que encaje con la ocasión. |
| string | opcional | Estilo musical. Sin indicación elegimos uno que encaje con la ocasión y el ambiente. |
| string | opcional | Voz cantante. Sin indicación elegimos una que encaje con la ocasión. |
| string | opcional | Un mensaje que debe aparecer en la canción. |
| string | opcional | Texto libre para todo lo que no cabe en otro sitio, por ejemplo deseos sobre el tempo. |
| string | opcional | Dirección HTTPS a la que enviar los eventos. |
| object | opcional | Pares clave-valor libres, 20 como máximo. Vuelven sin cambios. |
const res = await fetch("https://api.cancionalmedida.es/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: "es",
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);
La llamada no cuesta nada. Se paga solo al desbloquear con /unlock o con una sesión de pago completada.
Todo endpoint que devuelve una canción concreta devuelve el mismo objeto. Los campos que todavía no existen valen null y se van rellenando durante la producción.
{
"id": "sng_3n8Kd2ZpQv",
"object": "song",
"status": "queued",
"created_at": "2026-09-16T09:41:02Z",
"occasion": "birthday",
"recipient_name": "Anna",
"relationship": "sister",
"language": "es",
"mood": "happy",
"style": "pop",
"voice": "female",
"lyrics": null,
"preview_url": null,
"audio_url": null,
"duration_seconds": null,
"paid": false,
"price": { "amount": 2999, "currency": "EUR" },
"metadata": {},
"livemode": true
}
| Campo | Tipo | Descripción |
|---|---|---|
| string | opcional | Identificador único, siempre empieza por sng_. |
| string | opcional | Estado de producción actual, véase la sección siguiente. |
| string | opcional | La letra completa con las marcas de estrofa y estribillo. Gratuita, también sin pago. |
| string | opcional | Los primeros 45 segundos en MP3. Siempre disponibles, sin pago. |
| string | opcional | La grabación completa en MP3, firmada y válida 24 horas. Se rellena solo tras el pago. |
| integer | opcional | Duración de la grabación terminada en segundos, normalmente entre 120 y 240. |
| boolean | opcional | Si la canción está desbloqueada. |
| object | opcional | Importe en la unidad monetaria menor más el código de moneda, aquí 29,99 €. |
| object | opcional | Lo que enviaste al crearla, sin cambios. |
| boolean | opcional | false si la petición se hizo con una clave de prueba. |
{
"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.cancionalmedida.es/preview/sng_3n8Kd2ZpQv.mp3",
"audio_url": "https://cdn.cancionalmedida.es/full/sng_3n8Kd2ZpQv.mp3?expires=1789412855&sig=...",
"duration_seconds": 184,
"paid": true,
"price": { "amount": 2999, "currency": "EUR" },
"metadata": { "order_id": "A-10423" },
"livemode": true
}
Una canción recorre estos estados en este orden. Nunca retrocede, y complete, failed y cancelled son estados finales.
| Estado | Valor | Significado |
|---|---|---|
| queued | Aceptada, esperando un hueco de producción libre. Normalmente unos segundos. | |
| writing_lyrics | Se está escribiendo la letra. | |
| lyrics_ready | La letra está terminada y se puede recuperar. Suele ser tras uno o dos minutos. | |
| generating_audio | Voz, arreglo y mezcla están en producción. | |
| preview_ready | Los primeros 45 segundos están disponibles y el archivo completo está listo. | |
| complete | Pagada y entregada por completo. | |
| failed | La producción falló definitivamente. No se cobra nada, el campo error indica el motivo. | |
| cancelled | Cancelada antes de terminar. |
Un intento de producción fallido no lleva directamente a failed. Internamente reintentamos varias veces y solo nos rendimos cuando fallan todos los intentos. Por eso failed es raro y significa de verdad: esta canción no va a llegar.
GET /v1/songs/{id} devuelve el estado actual de una canción. El endpoint es ligero y soporta consultas cada segundo, siempre dentro del límite de frecuencia.
curl -G https://api.cancionalmedida.es/v1/songs \
-H "Authorization: Bearer $SONG_API_KEY" \
-d status=complete \
-d limit=20 \
-d starting_after=sng_3n8Kd2ZpQv
Las listas van por cursor. Recibes como mucho limit entradas, 20 por defecto y 100 como máximo, empezando por las más recientes. Si has_more es verdadero, pasas next_cursor como starting_after en la siguiente llamada. Puedes filtrar por status, occasion, language, paid además de created_after y 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"
}
La letra es gratuita y completa, no un extracto. GET /v1/songs/{id}/lyrics la devuelve como text/plain, con las marcas de estrofa y estribillo. La misma letra está en el campo lyrics del objeto canción.
Para el audio hay dos niveles. La vista previa son los primeros 45 segundos de la grabación terminada, no una demo aparte: misma voz, mismo arreglo, misma letra. Está disponible sin pago y sigue estándolo. El archivo completo lo entrega GET /v1/songs/{id}/audio solo tras el desbloqueo.
Ambas direcciones están firmadas y son válidas 24 horas. Sirven para descargar, no para enlazar de forma permanente. Si necesitas un archivo más tiempo, descárgalo una vez y guárdalo tú. Una nueva llamada al endpoint da en cualquier momento una dirección fresca.
El formato es siempre MP3 a 320 kbit/s. Quien necesite WAV añade ?format=wav, disponible para cuentas con la opción de estudio.
Si un resultado no convence, regenerar no cuesta nada. POST /v1/songs/{id}/regenerate crea una versión nueva bajo el mismo identificador y devuelve el estado a queued. La versión anterior queda en previous_versions.
curl https://api.cancionalmedida.es/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."
}'
Con keep_lyrics: true la letra se mantiene y solo se rehace la grabación. Es el camino correcto cuando la letra funciona y solo la voz o el tempo no encajaban. Con false también se reescribe la letra.
El campo note entra directamente en la regeneración, así que una frase concreta compensa. "Voz masculina más grave, más lenta" funciona; "hazlo mejor" no. Tres regeneraciones por canción son gratuitas; más allá, háblalo con nosotros.
Hay dos formas de desbloquear una canción, según quién pague.
POST /v1/songs/{id}/checkout crea en nuestro lado una página de pago en la moneda del dominio, con los medios de pago habituales de ese país. Envías allí al cliente y recibes el evento song.paid cuando el pago cuaja.
curl https://api.cancionalmedida.es/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.cancionalmedida.es/c/cs_live_8Hd2Kq...",
"amount": 2999,
"currency": "EUR",
"expires_at": "2026-09-16T11:41:02Z"
}
En cuentas con facturación agrupada, POST /v1/songs/{id}/unlock desbloquea la canción al momento y carga 29,99 € a la cuenta. Sin rodeo por una página de pago, cómodo si tienes tu propia caja.
curl https://api.cancionalmedida.es/v1/songs/sng_3n8Kd2ZpQv/unlock \
-H "Authorization: Bearer $SONG_API_KEY" \
-X POST
En ambos casos rige el mismo derecho de uso: no exclusivo, pero expresamente comercial. Puedes ceder, vender y publicar la canción terminada dentro de tu oferta.
Las listas de ocasión, ambiente, estilo, voz e idioma cambian de vez en cuando. En lugar de escribirlas en el código, consulta GET /v1/options y guarda la respuesta en caché unas horas.
{
"object": "options",
"language": "es",
"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"]
}
Cualquiera de esos valores puede omitirse. El valor surprise_me no es un relleno sino una instrucción real: en ese caso elegimos a conciencia algo que encaje con la ocasión y los detalles.
Indica al crear una callback_url y enviaremos allí cada evento por POST. Es el camino recomendado porque ahorra consultas repetidas y espera.
| Evento | Tipo | Se dispara cuando |
|---|---|---|
| song.lyrics_ready | La letra está terminada. | |
| song.preview_ready | La vista previa de 45 segundos está disponible. | |
| song.completed | La grabación completa se ha entregado. | |
| song.failed | La producción falló definitivamente. | |
| song.regenerated | Una regeneración está lista. | |
| song.paid | El pago ha llegado, la canción está desbloqueada. |
{
"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.cancionalmedida.es/full/sng_3n8Kd2ZpQv.mp3?expires=1789412855&sig=...",
"...": "..."
}
}
}
Cada entrega lleva una cabecera con marca de tiempo y HMAC-SHA256 sobre marca de tiempo, punto y cuerpo en bruto. Compruébala antes de fiarte del contenido y descarta todo lo que tenga más de cinco minutos.
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
Esperamos una respuesta 2xx en diez segundos. Si no llega, reintentamos ocho veces a lo largo de 24 horas con intervalos crecientes. Las entregas pueden por tanto repetirse y, rara vez, llegar desordenadas: haz tu endpoint idempotente y fíate de created_at, no de la hora de llegada.
Cada POST acepta la cabecera Idempotency-Key con cualquier valor único, normalmente un UUID. Si la misma clave vuelve dentro de 24 horas, devolvemos la respuesta original en lugar de crear una segunda canción.
curl https://api.cancionalmedida.es/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": "es", "details": "..." }'
Es justo la protección que hace falta contra los fallos de red: si una respuesta se pierde y tu código repite la petición, sigue naciendo una sola canción. Si envías la misma clave con un cuerpo distinto respondemos 409 con el tipo de error conflict.
Los errores llegan siempre con la misma forma, con type y code legibles por máquina, un mensaje legible y, cuando procede, el campo afectado. Incluye la request_id en cualquier consulta de soporte: así encontramos la llamada en los registros.
{
"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"
}
}
| Tipo | HTTP | Significado |
|---|---|---|
| 400 | invalid_request | La petición está formalmente rota, por ejemplo JSON inválido o un campo desconocido. |
| 401 | authentication_error | La clave falta, ha caducado o está bloqueada. |
| 403 | permission_error | La clave es válida pero no está habilitada para este dominio o endpoint. |
| 404 | not_found | El identificador solicitado no pertenece a esta cuenta o no existe. |
| 409 | conflict | La acción no encaja con el estado, por ejemplo desbloquear una canción cancelada. |
| 422 | validation_error | La petición es formalmente correcta pero un valor no sirve, por ejemplo detalles demasiado cortos. |
| 429 | rate_limit | Demasiadas peticiones o demasiadas producciones a la vez. |
| 500 | api_error | Fallo de nuestro lado. Reintenta con intervalos crecientes. |
Con 429 y 5xx reintentar tiene sentido, preferiblemente con intervalos exponenciales y algo de azar. Con un 4xx distinto de 429 no lo tiene: la misma petición volverá a fallar.
| Límite | Valor | Se aplica a |
|---|---|---|
| 60 / min | Peticiones por minuto y clave en todos los endpoints. | |
| 10 | Producciones simultáneas. Las peticiones adicionales quedan en cola. | |
| 64 KB | Tamaño máximo del cuerpo de una petición. | |
| 40 - 4000 | Caracteres en el campo details, mínimo y máximo. | |
| 90 | Días que conservamos canciones y datos introducidos, después se borran. | |
| 24 h | Tiempo durante el cual una clave de idempotencia devuelve la respuesta antigua. |
Cada respuesta lleva el estado actual en las cabeceras, así que no tienes que adivinar.
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 57
X-RateLimit-Reset: 1789412880
X-Concurrent-Limit: 10
X-Concurrent-Running: 3
Límites más altos no son problema, simplemente no son el ajuste de partida. Si el volumen crece, escríbenos dos líneas y los subimos.
La versión mayor está en la ruta y se mantiene estable. Dentro de v1 solo llegan cambios aditivos: campos nuevos, valores nuevos en las listas, endpoints nuevos. Los campos existentes no desaparecen ni cambian de significado.
Para mayor seguridad puedes fijar una fecha en una cabecera. Sin cabecera rige siempre el comportamiento más reciente.
X-Song-Version: 2026-09-01
Tu código debería ignorar los campos desconocidos en las respuestas en lugar de romperse. Es la única suposición que hacemos sobre los clientes.
Las claves con el prefijo sk_test_ pasan exactamente por los mismos endpoints, pero no arrancan ninguna producción real y no cuestan nada. A los pocos segundos recibes una letra de demostración fija y una grabación de demostración, y cada objeto lleva livemode: false.
Así también se pueden probar los casos incómodos. Ciertos nombres en el campo recipient_name fuerzan un desenlace concreto: test_fail lleva a failed, test_slow a una producción de unos diez minutos, test_ratelimit a una respuesta 429. Puedes probar tu gestión de errores sin esperar a una avería real.
Los webhooks también funcionan en modo de prueba, con el mismo mecanismo de firma y un secreto propio.
Con el desbloqueo obtienes un derecho de uso no exclusivo pero expresamente comercial sobre la canción terminada. Puedes cederla, venderla, interpretarla en público e integrarla en tu producto. No exclusivo significa que conservamos el derecho a usar nosotros mismos la grabación, por ejemplo como ejemplo.
Sobre los derechos de autor de la música creada con inteligencia artificial, muchos ordenamientos aún no tienen una respuesta definitiva. Te garantizamos el uso por contrato, pero no podemos asegurar que sobre la grabación nazca un derecho de autor propio oponible a terceros. Quien dependa de ello debería hacerlo revisar de antemano.
Conservamos los datos introducidos y las canciones terminadas 90 días; después se borran. Para borrar antes una canción concreta está DELETE /v1/songs/{id}. La información de details la usamos solo para producir esa canción y nunca para entrenar modelos propios.
Si nos envías datos de tus clientes, tú eres el responsable del tratamiento y nosotros el encargado. Hay un contrato de encargo disponible si lo pides.
Preguntas, límites más altos, contrato de encargo, casos especiales: songs@maxkuch.com. Para problemas técnicos indica la request_id de la respuesta de error y encontramos la llamada al momento.
Para integraciones con agentes de inteligencia artificial existe además un servidor Model Context Protocol, documentado en /mcp/, que usa las mismas claves que la API REST.
Las claves se entregan a mano. Basta un correo breve con tu proyecto, el volumen previsto y los idiomas, y la activación suele tardar un día laborable.