Ir al contenido
Euphona

Desarrolladores

El motor de Euphona, como API

Un único contrato REST sobre api.euphonaai.com/v1. Tareas asíncronas con progreso en vivo por SSE, transferencia de archivos firmada, claves con prefijo y un modo de prueba gratuito: un producto propio, con su propio período de soporte de doce meses.

Las claves se crean en la configuración del Studio: eu_test_… al instante y gratis, eu_live_… desde la misma pantalla una vez que hay una tarjeta guardada, con cobro por uso desde su primera tarea. El modo de prueba ejecuta el contrato completo.

Todo es una tarea

curl -X POST https://api.euphonaai.com/v1/masters \
  -H "Authorization: Bearer $EUPHONA_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"file_id": "file_8Kd2mQTz3XaV7pLw"}'

HTTP/2 202
{
  "id": "job_7Vd2mHs5Yq8Tz3Xa",
  "object": "job",
  "type": "mastering",
  "status": "queued",
  "stage": null,
  "metering": {
    "pool": "payg",
    "charged_minutes": 0,
    "test_mode": false
  }
}

Diseño

Cómo está diseñada

Cuatro decisiones que no se negocian, porque cada una marca la diferencia entre una integración que sobrevive a su primera caída y una que no.

Todo es una tarea

Los endpoints de las capacidades devuelven 202 y un recurso de tarea. No hay endpoints de procesamiento síncronos ni los habrá nunca: una canción de tres minutos con calidad de masterización no es un ciclo de solicitud y respuesta, y una API que finge lo contrario enseña a escribir mal cada integración.

El audio rodea la API, no pasa por ella

POST /v1/files devuelve un destino de subida firmado y reanudable en files.euphonaai.com. Los resultados vuelven como URL de descarga firmadas que caducan. La API en sí es un plano de control JSON y nunca transporta un solo byte de audio.

Esta API es el producto, no un subproducto

Euphona Studio habla otra API, propia. Es deliberado: son dos productos separados que comparten un motor, así que un cambio que quiera la app no puede llegar a este contrato, y el período de soporte de doce meses de este contrato no es un peaje que la app tenga que pagar. La consecuencia honesta es que la app puede hacer cosas que esta API no hace: cuando eso te importe, pregúntanos, y se convierte en un punto de la hoja de ruta en lugar de una sorpresa.

El documento es el contrato

El documento OpenAPI 3.1 es la fuente de verdad. Las rutas del servidor y los tipos se generan a partir de él, y una prueba que bloquea el despliegue mantiene las rutas servidas iguales a las operaciones declaradas: así que una divergencia entre lo documentado y lo servido hace fallar la compilación en lugar de ser algo que prometemos vigilar. Los SDK generados a partir del mismo documento llegan con la versión preliminar para desarrolladores.

Recorrido

La forma de una integración

Crea un archivo, envía los bytes al destino firmado, envía una tarea y sigue por SSE hasta que termine. Cuatro pasos, y el tercero es la única parte que cambia según la capacidad.

1. Crea el archivo y obtén un destino de subida

curl -X POST https://api.euphonaai.com/v1/files \
  -H "Authorization: Bearer $EUPHONA_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"filename":"take-07.wav","bytes":58210044,"content_type":"audio/wav"}'

{
  "id": "file_8Kd2mQ...",
  "object": "file",
  "status": "awaiting_upload",
  "upload": { "protocol": "...", "url": "https://files.euphonaai.com/...", "expires_at": "..." }
}

2. Envía los bytes a files., no a nosotros

# Lee upload.protocol, no des por supuesto cuál es. El mecanismo reanudable es una
# decisión de implementación dentro del contrato y puede cambiar sin una nueva
# versión mayor, que es justo por lo que es un campo y no documentación.

3. Sigue la tarea

curl -N https://api.euphonaai.com/v1/jobs/job_4Rb.../events \
  -H "Authorization: Bearer $EUPHONA_API_KEY"

event: job.progress
data: {"status":"analyzing","stage":"analyze","progress":{"fraction":0.4}}

event: job.completed
data: {"status":"succeeded","result":{...},"metering":{"pool":"payg","charged_minutes":3.4}}

4. Verifica contra la propia tarea

curl https://api.euphonaai.com/v1/jobs/job_4Rb... \
  -H "Authorization: Bearer $EUPHONA_API_KEY"

{"id":"job_4Rb...","status":"succeeded","result":{...},
 "metering":{"pool":"payg","charged_minutes":3.4}}

# El recurso de la tarea es la verdad actual de la máquina de estados: muestre lo
# que muestre un stream, o lo que se haya perdido, vuelve a leerlo antes de actuar.
# Una conexión SSE caída se reconecta con Last-Event-ID y no pierde nada.

Referencia

La superficie

La superficie de la plataforma que comparten todas las capacidades. Cada capacidad añade un endpoint de creación; la tarea que devuelve pasa por todo lo de abajo: el mismo ciclo de vida, los mismos eventos, el mismo informe.

Endpoints del contrato v1
MétodoRutaQué hace
POST/v1/filesCrea un archivo y obtén un destino de subida firmado y reanudable.
GET/v1/files/{id}Estado de la ingesta, duración medida, frecuencia de muestreo, canales y formato.
DELETE/v1/files/{id}Elimina un archivo y lo que se hizo a partir de él; se rechaza mientras una tarea lo siga usando.
GET/v1/files/{id}/downloadUna URL de descarga firmada que caduca.
GET/v1/jobsLa superficie de lectura unificada, paginada con cursor.
GET/v1/jobs/{id}Estado, etapa, progreso, consumo y el informe medido.
POST/v1/jobs/{id}/cancelCancela; no hay cargo antes de que empiece la etapa de procesamiento.
POST/v1/jobs/{id}/inputResponde a lo que pregunta una tarea en estado awaiting_input y la reanuda. No consume minutos.
GET/v1/jobs/{id}/eventsEventos enviados por el servidor durante toda la vida de la tarea.
POST/v1/projectsAgrupa archivos y tareas; el Track Graph es por proyecto.
GET/v1/projects/{id}Un proyecto, con los ids de sus archivos y tareas.
GET/v1/usageEl estado de la cuota y las dos bolsas de consumo.
GET/v1/webhook-endpointsTus endpoints de webhook, en el modo de esta clave.
POST/v1/webhook-endpointsRegistra una URL https; responde a un desafío firmado antes de que se guarde nada.
GET/v1/webhook-endpoints/{id}Un endpoint. Su secreto no se vuelve a mostrar.
PATCH/v1/webhook-endpoints/{id}Cambia sus eventos, su descripción o su URL; una URL nueva se vuelve a verificar.
DELETE/v1/webhook-endpoints/{id}Elimínalo; los eventos pendientes de entregar para él se descartan.
POST/v1/webhook-endpoints/{id}/rotate-secretUn nuevo secreto de firma; el anterior sigue firmando durante un periodo de transición.
GET/v1/eventsTreinta días de eventos, con el resultado de cada entrega.
GET/v1/events/{id}Un evento tal como se entregó, con sus entregas.

Webhooks

Entérate de cuándo termina una tarea

Notificaciones firmadas cuando una tarea termina o espera tu respuesta, para que una integración no tenga que mantener un stream abierto ni hacer polling.

Eventos
job.completed, job.failed, job.canceled y job.awaiting_input. Cada uno es un evento cuyo data es el recurso de la tarea exactamente como lo devuelve el polling.
Eventos del espacio de trabajo
file.expiring cuando un archivo subido y guardado recibe una fecha a partir de la cual puede eliminarse, con el archivo como data, y usage.threshold cuando un saldo de uso llega al 80 % o al 100 % de su límite. Ambos llegan a endpoints de cualquier modo, una vez por archivo o una vez por umbral y periodo.
Registro
Requiere una clave con el alcance webhooks:manage, que las claves nuevas no tienen. La URL debe usar https y resolver solo a direcciones públicas, y debe responder a un desafío firmado con una respuesta 2xx que lo devuelva antes de que se guarde nada. Las entregas se envían solo por IPv4, así que el host debe tener una dirección IPv4.
Entrega
Al menos una vez, así que deduplica por Euphona-Event-Id. Cualquier cosa que no sea un 2xx en 10 segundos se reintenta con backoff exponencial durante 24 horas; después pasa a la cola de mensajes fallidos y queda en GET /v1/events. Las redirecciones nunca se siguen y el orden no está garantizado: concilia con la tarea.
Firmas
Euphona-Signature lleva una marca de tiempo y un HMAC-SHA256 de la marca de tiempo, un punto y el cuerpo sin modificar, con el secreto del endpoint como clave. Verifícalo antes de analizar el JSON, compara en tiempo constante y rechaza una marca de tiempo que se aleje más de cinco minutos de tu reloj.
Direcciones de origen
Las entregas se envían desde 65.21.69.162. Permítelas si tu firewall filtra el tráfico entrante y, aun así, verifica la firma: la dirección de la que llega una solicitud no demuestra quién la envió.

Verificar una entrega (Node.js)

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

// Pasa el cuerpo exactamente como llegó: un JSON analizado y vuelto a
// serializar no es el mensaje que se firmó.
export function verifyEuphonaSignature(rawBody, header, secret, now = Date.now() / 1000) {
  const parts = header.split(",").map((part) => part.trim().split("="));
  const t = Number(parts.find(([key]) => key === "t")?.[1]);
  const signatures = parts.filter(([key]) => key === "v1").map(([, value]) => value);
  if (!Number.isFinite(t) || Math.abs(now - t) > 300) return false;
  const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest();
  return signatures.some((value) => {
    const given = Buffer.from(value, "hex");
    return given.length === expected.length && timingSafeEqual(given, expected);
  });
}

Operaciones

Lo que te muerde a las tres de la mañana

Los seis comportamientos que vale la pena leer antes de escribir la integración, y no después de que te despierten.

Claves
eu_live_… y eu_test_…, propiedad de un espacio de trabajo, se muestran una sola vez al crearlas y se guardan con hash. Rótalas con un período de superposición de hasta treinta días, para que una rotación no sea una caída. Las claves de prueba son gratis dentro de su propio límite mensual y se ejecutan en su propia clase de planificación; devuelven una vista previa en lugar de un máster, así que se puede construir una integración de punta a punta sin comprar nada.
Errores
RFC 9457 application/problem+json, siempre, con un code estable y legible por máquina, un detail para personas y el ID de solicitud. Ramifica según code, nunca según el texto: el texto está localizado y el código es un contrato.
Idempotencia
Idempotency-Key en cada POST. La misma clave con el mismo cuerpo repite la respuesta original mientras exista el recurso que creó; la misma clave con un cuerpo distinto se rechaza. Así, un reintento después de un timeout devuelve la tarea original en lugar de iniciar una segunda que se te cobraría.
Límites
Ventana deslizante por clave con los encabezados estándar RateLimit-*; 60 solicitudes por minuto con pago por uso, más con un contrato de uso comprometido. El limitador regula solo la frecuencia de las solicitudes: el rendimiento lo regulan tu cuota y la cola.
Dos contadores, nunca uno
Las tareas con clave de API consumen de la bolsa de pago por uso; las tareas de una sesión del Studio consumen minutos del plan. Ninguna bolsa puede vaciar la otra, y GET /v1/usage informa de ambas. Los límites de gasto vienen activados por defecto. El almacenamiento es un solo cupo, no dos: los archivos subidos con una clave y los resultados de las tareas de la API cuentan, junto con todo lo del Studio, para el cupo de almacenamiento del plan del espacio de trabajo, 2 GB en el plan gratuito, y una subida o una tarea que no quepa se rechaza con storage_full.
Request-Id
En cada respuesta, con éxito o con error. Indícalo en una solicitud de soporte y se rastrea de punta a punta: a través de la tarea y de cada etapa, hasta lo que realmente pasó.
Errores
codetitle
unauthenticatedSe requiere autenticación
invalid_keyClave de API no válida
key_revokedClave de API revocada
insufficient_scopeAlcance insuficiente
email_verification_requiredSe requiere verificar el correo
malformed_requestSolicitud mal formada
validation_failedLa validación de la solicitud falló
unsupported_media_typeTipo de medio no admitido
idempotency_key_reuseClave de idempotencia reutilizada con otro cuerpo
not_foundNo encontrado
job_not_cancelableLa tarea no se puede cancelar
job_not_awaiting_inputLa tarea no está esperando una respuesta
file_not_readyEl archivo no está listo
file_expiredEl archivo caducó
file_in_useArchivo en uso
file_disabledArchivo no disponible por motivos legales
unsupported_formatFormato de audio no admitido
unsupported_sample_rateFrecuencia de muestreo no admitida
unsupported_channel_countNúmero de canales no admitido
file_too_longEl audio es demasiado largo
file_too_shortEl audio es demasiado corto
file_too_largeEl archivo es demasiado grande
corrupt_mediaNo se pudo decodificar el audio
rate_limitedLímite de solicitudes superado
too_many_active_jobsDemasiadas tareas activas
quota_exceededCuota superada
spend_cap_reachedLímite de gasto alcanzado
test_mode_cap_reachedLímite del modo de prueba alcanzado
storage_fullAlmacenamiento lleno
purchase_requiredSe requiere una compra
payment_method_requiredSe requiere un método de pago
format_not_in_planFormato no incluido en este plan
webhook_url_not_httpsLa URL del webhook debe ser HTTPS
webhook_url_not_publicLa URL del webhook debe ser enrutable públicamente
webhook_verification_failedFalló la verificación de propiedad del webhook
internal_errorError interno
service_unavailableServicio no disponible
processing_failedEl procesamiento falló
capacity_unavailableCapacidad no disponible
capability_pausedEn pausa temporalmente
workspace_suspendedEspacio de trabajo suspendido
uploads_blockedSubidas en pausa
reauthentication_requiredConfirma que eres tú

Tarifas

Cuánto cuesta

Por minuto procesado, multiplicado por el multiplicador de la capacidad, con facturación mensual. No hace falta suscripción: un espacio de trabajo de API puede estar en el plan gratuito. El modo de prueba es gratis.

Primeros 100 minutos al mes
0,35 US$
Hasta 1000
0,25 US$
Hasta 10.000
0,15 US$
A partir de ahí, o uso comprometido
Habla con nosotros