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
El audio rodea la API, no pasa por ella
Esta API es el producto, no un subproducto
El documento es el contrato
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.
| Método | Ruta | Qué hace |
|---|---|---|
| POST | /v1/files | Crea 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}/download | Una URL de descarga firmada que caduca. |
| GET | /v1/jobs | La superficie de lectura unificada, paginada con cursor. |
| GET | /v1/jobs/{id} | Estado, etapa, progreso, consumo y el informe medido. |
| POST | /v1/jobs/{id}/cancel | Cancela; no hay cargo antes de que empiece la etapa de procesamiento. |
| POST | /v1/jobs/{id}/input | Responde a lo que pregunta una tarea en estado awaiting_input y la reanuda. No consume minutos. |
| GET | /v1/jobs/{id}/events | Eventos enviados por el servidor durante toda la vida de la tarea. |
| POST | /v1/projects | Agrupa 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/usage | El estado de la cuota y las dos bolsas de consumo. |
| GET | /v1/webhook-endpoints | Tus endpoints de webhook, en el modo de esta clave. |
| POST | /v1/webhook-endpoints | Registra 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-secret | Un nuevo secreto de firma; el anterior sigue firmando durante un periodo de transición. |
| GET | /v1/events | Treinta 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.canceledyjob.awaiting_input. Cada uno es un evento cuyodataes el recurso de la tarea exactamente como lo devuelve el polling.- Eventos del espacio de trabajo
file.expiringcuando un archivo subido y guardado recibe una fecha a partir de la cual puede eliminarse, con el archivo comodata, yusage.thresholdcuando 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 enGET /v1/events. Las redirecciones nunca se siguen y el orden no está garantizado: concilia con la tarea. - Firmas
Euphona-Signaturelleva 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_…yeu_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 uncodeestable y legible por máquina, undetailpara personas y el ID de solicitud. Ramifica segúncode, nunca según el texto: el texto está localizado y el código es un contrato. - Idempotencia
Idempotency-Keyen 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/usageinforma 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 constorage_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ó.
code | title |
|---|---|
| unauthenticated | Se requiere autenticación |
| invalid_ | Clave de API no válida |
| key_ | Clave de API revocada |
| insufficient_ | Alcance insuficiente |
| email_ | Se requiere verificar el correo |
| malformed_ | Solicitud mal formada |
| validation_ | La validación de la solicitud falló |
| unsupported_ | Tipo de medio no admitido |
| idempotency_ | Clave de idempotencia reutilizada con otro cuerpo |
| not_ | No encontrado |
| job_ | La tarea no se puede cancelar |
| job_ | La tarea no está esperando una respuesta |
| file_ | El archivo no está listo |
| file_ | El archivo caducó |
| file_ | Archivo en uso |
| file_ | Archivo no disponible por motivos legales |
| unsupported_ | Formato de audio no admitido |
| unsupported_ | Frecuencia de muestreo no admitida |
| unsupported_ | Número de canales no admitido |
| file_ | El audio es demasiado largo |
| file_ | El audio es demasiado corto |
| file_ | El archivo es demasiado grande |
| corrupt_ | No se pudo decodificar el audio |
| rate_ | Límite de solicitudes superado |
| too_ | Demasiadas tareas activas |
| quota_ | Cuota superada |
| spend_ | Límite de gasto alcanzado |
| test_ | Límite del modo de prueba alcanzado |
| storage_ | Almacenamiento lleno |
| purchase_ | Se requiere una compra |
| payment_ | Se requiere un método de pago |
| format_ | Formato no incluido en este plan |
| webhook_ | La URL del webhook debe ser HTTPS |
| webhook_ | La URL del webhook debe ser enrutable públicamente |
| webhook_ | Falló la verificación de propiedad del webhook |
| internal_ | Error interno |
| service_ | Servicio no disponible |
| processing_ | El procesamiento falló |
| capacity_ | Capacidad no disponible |
| capability_ | En pausa temporalmente |
| workspace_ | Espacio de trabajo suspendido |
| uploads_ | Subidas en pausa |
| reauthentication_ | Confirma 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