Integra análisis emocional de voz en tu producto
Envía audio, consulta el estado del análisis y descarga el informe normalizado desde tu propio backend. La misma inteligencia que usa el portal de Voice Feeling, expuesta como API REST.
De un archivo de audio a un informe
Cuatro llamadas cubren el flujo completo: crear el análisis, consultar su estado, y descargar el informe normalizado cuando termine.
Crear un análisis subiendo el archivo
Envía el audio como multipart/form-data. `consentObtained=true` es obligatorio: confirma que cuentas con consentimiento o base legal para analizar la grabación.
curl https://voice-feeling.pages.dev/api/v1/analyses \ -H "Authorization: Bearer vf_live_xxxxxxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \ -H "Idempotency-Key: 5f3d2b7e-9c2e-4f6a-9c0e-1a2b3c4d5e6f" \ -F "file=@call.wav" \ -F "consentObtained=true" \ -F "businessContext=sales"Crear un análisis desde una URL
Si el audio ya está alojado en tu infraestructura, envía JSON con `audioUrl` en lugar de subir el archivo. El mismo `consentObtained: true` sigue siendo obligatorio.
curl https://voice-feeling.pages.dev/api/v1/analyses \ -H "Authorization: Bearer vf_live_xxxxxxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \ -H "Idempotency-Key: 5f3d2b7e-9c2e-4f6a-9c0e-1a2b3c4d5e6f" \ -H "Content-Type: application/json" \ -d '{ "audioUrl": "https://cdn.example.com/calls/2026-09-03-0142.wav", "consentObtained": true, "businessContext": "service" }'Consultar el estado
El análisis pasa por varios estados hasta `completed` o `failed`. Haz polling con backoff o regístrate a un webhook para que te avisemos.
curl https://voice-feeling.pages.dev/api/v1/analyses/an_9f3c2e1a \ -H "Authorization: Bearer vf_live_xxxxxxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" # { # "id": "an_9f3c2e1a", # "status": "completed", # "businessContext": "service", # "createdAt": "2026-09-03T14:02:11Z", # "completedAt": "2026-09-03T14:03:47Z", # "reportUrl": "/api/v1/analyses/an_9f3c2e1a/report" # }Descargar el informe normalizado
Disponible solo cuando el análisis está en `completed`. El informe sigue el contrato `voicefeeling.report.v1`; nunca contiene la respuesta cruda del proveedor.
curl https://voice-feeling.pages.dev/api/v1/analyses/an_9f3c2e1a/report \ -H "Authorization: Bearer vf_live_xxxxxxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
Envía una cabecera `Idempotency-Key` en `POST /analyses` para que reintentos de red no dupliquen el análisis.
Llaves de API y scopes
Autentica cada solicitud con una llave de API en la cabecera Authorization. Genera y administra tus llaves desde el portal, en la sección Desarrolladores.
- Cabecera requerida
Authorization: Bearer vf_live_<prefix>_<secret>- Formato de la llave
- vf_live_<prefijo>_<secreto>. El prefijo identifica la llave en los registros; el secreto solo se muestra una vez al crearla.
Scopes disponibles
Cada llave declara los scopes que necesita. Una solicitud sin el scope requerido recibe `api_scope_missing` con el scope faltante en la respuesta.
analyses:readConsultar análisis, su estado y el informe normalizado.analyses:writeCrear análisis a partir de audio subido o de una URL.audios:writeSubir archivos de audio como parte de la creación de un análisis.webhooks:manageRegistrar, listar y eliminar endpoints de webhook.usage:readConsultar el consumo de la organización frente a sus límites.
Reintentos seguros
Toda entrega puede repetirse: si tu solicitud de red falla sin respuesta clara, reenvíala con la misma cabecera `Idempotency-Key`. Si la llave ya se usó con un cuerpo distinto, la API responde `idempotency_conflict` en lugar de crear un segundo análisis.
Notificaciones de eventos
En vez de hacer polling, registra un endpoint HTTPS para recibir eventos cuando un análisis termine.
Eventos disponibles
analysis.completedEl análisis terminó y el informe normalizado está disponible.analysis.failedEl análisis terminó en error tras agotar los reintentos.
Forma del payload
Cada entrega es un POST en JSON con cabeceras de identificación y firma.
POST https://your-domain.com/webhooks/voicefeeling
Content-Type: application/json
Voice-Feeling-Event: analysis.completed
Voice-Feeling-Signature: t=1756900931,v1=4b1f9e...c2a0
{
"event": "analysis.completed",
"analysisId": "an_9f3c2e1a",
"createdAt": "2026-09-03T14:03:47Z"
}Verifica la firma
Calcula el HMAC-SHA256 del timestamp y el cuerpo con tu secreto, y compáralo con la firma recibida antes de confiar en el payload. Rechaza entregas con más de 5 minutos de diferencia entre el timestamp y tu reloj.
const crypto = require("node:crypto");
function isValidSignature(header, rawBody, secret, toleranceSeconds = 300) {
const parts = Object.fromEntries(
header.split(",").map((part) => part.split("="))
);
const timestamp = Number(parts.t);
if (!timestamp || Math.abs(Date.now() / 1000 - timestamp) > toleranceSeconds) {
return false;
}
const expected = crypto
.createHmac("sha256", secret)
.update(`${timestamp}.${rawBody}`)
.digest("hex");
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1 || ""));
}Reintentos y desactivación
Las entregas fallidas se reintentan con backoff exponencial durante aproximadamente una hora. Un endpoint con 20 fallos consecutivos se deshabilita automáticamente; vuelve a activarlo eliminándolo y registrándolo de nuevo desde el portal.
Códigos de error
Toda respuesta de error usa el mismo sobre: `{ "error": { "code", "message", "docs_url" } }`. Diseña tu integración contra `code`, no contra `message`, que puede cambiar de idioma según `Accept-Language`.
| CÓDIGO | HTTP | DESCRIPCIÓN |
|---|---|---|
api_key_invalid | 401 | La llave no existe o no coincide con ninguna registrada. |
api_key_expired | 401 | La llave tenía fecha de expiración y ya se cumplió. |
api_key_revoked | 401 | La llave fue revocada desde el portal. |
api_scope_missing | 403 | La llave no tiene el scope requerido; la respuesta incluye `required_scope`. |
api_rate_limited | 429 | Se superó el límite de solicitudes por llave; respeta la cabecera `Retry-After`. |
api_disabled | 503 | La API pública está deshabilitada en este entorno. |
consent_required | 422 | Falta `consentObtained: true` en la solicitud de creación del análisis. |
audio_url_invalid | 422 | `audioUrl` no es una URL https válida o pública. |
audio_download_failed | 422 | No se pudo descargar el audio desde `audioUrl`. |
unsupported_audio_content | 415 | El contenido del archivo no corresponde a un formato de audio soportado. |
audio_too_large | 413 | El archivo supera el límite de tamaño permitido. |
monthly_analysis_quota_exceeded | 429 | Se alcanzó la cuota mensual de análisis del plan. |
daily_upload_quota_exceeded | 429 | Se alcanzó la cuota diaria de cargas del plan. |
storage_quota_exceeded | 413 | El almacenamiento de la organización alcanzó su límite. |
idempotency_conflict | 409 | La `Idempotency-Key` ya se usó con un cuerpo de solicitud distinto. |
analysis_not_found | 404 | No existe un análisis con ese id para esta organización. |
report_requires_final | 409 | El análisis todavía no terminó; el informe no está disponible. |
webhook_url_invalid | 422 | La URL del webhook no es una URL https pública válida. |
webhook_endpoint_not_found | 404 | No existe un endpoint de webhook con ese id para esta organización. |
webhook_limit_reached | 409 | Se alcanzó el número máximo de endpoints de webhook por organización. |
Límites técnicos
Diseña tu integración con estos límites en mente; superarlos produce el código de error correspondiente, no un corte silencioso.
- Frecuencia de solicitudes
- 120 solicitudes / 10 minutos por llave
- Tamaño máximo de audio
- 50 MiB por archivo
- Formatos soportados
- WAV, MP3, M4A, OGG, FLAC, WebM, AAC, AIFF
Voice Feeling mide señales acústicas (energía, tensión, ritmo) correlacionadas con estados emocionales. No son hechos psicológicos, no determinan intención y en ningún caso constituyen un veredicto de verdad o mentira. Toda decisión que afecte a una persona debe combinarse con contexto humano y las políticas de tu organización.
