API de Metadatos de Llamadas
📞 API de Metadatos de Llamadas
Sección titulada «📞 API de Metadatos de Llamadas»Los informes de call center necesitan datos que no están en el audio: cuándo ocurrió la llamada, qué agente la atendió, si fue entrante o saliente. Hay tres formas de aportarlos:
- Por archivo al subirlo, como campos adicionales en
POST /api/files/— ideal cuando su sistema sube las grabaciones una por una - Por archivo después, con
PATCH /api/files/{id}/— para correcciones y para metadatos que llegan después de la grabación - Por proyecto, como manifiesto CSV en
POST /api/projects/{id}/call-manifest/— ideal para cargas masivas y para completar metadatos a posteriori
Las tres escriben en el mismo lugar, así que puede combinarlas; prevalece la última escritura. Los metadatos enviados por la API siempre tienen prioridad sobre el nombre del agente que la IA extrae de la conversación.
Los metadatos que tiene una llamada en cada momento se pueden consultar en el recurso del archivo, en call_metadata.
Todos los endpoints requieren una cookie de sesión o una clave de API — consulte Autenticación.
Metadatos por archivo en POST /api/files/
Sección titulada «Metadatos por archivo en POST /api/files/»El endpoint de subida de archivos acepta metadatos de llamada opcionales junto a los campos habituales (consulte API de Transcripción para la petición base).
Content-Type: multipart/form-data
Campos adicionales del formulario:
| Campo | Tipo | Descripción |
|---|---|---|
call_datetime | fecha y hora | Cuándo ocurrió la llamada (ISO 8601, ej. 2026-06-15T09:12:00Z) |
agent_id | texto (≤64) | Identificador del agente en sus propios sistemas |
agent_name | texto (≤255) | Nombre visible del agente |
direction | texto | inbound o outbound |
caller_number | texto (≤64) | Número telefónico del cliente |
campaign | texto (≤255) | Nombre de la campaña o cola |
Los campos que no envíe simplemente no se establecen. Aquí los valores vacíos se ignoran, de modo que un campo en blanco nunca tapa una fila de manifiesto que ya esté esperando a este archivo.
Ejemplo:
curl -X POST https://app.uspeech.io/api/files/ \ -H "Authorization: Api-Key $USPEECH_KEY" \ -F "project=312" \ -F "file_type=audio" \ -F "file=@./call_001.mp3" \ -F "call_datetime=2026-06-15T09:12:00Z" \ -F "agent_id=A-06" \ -F "agent_name=Ana Diaz" \ -F "direction=inbound" \ -F "caller_number=+34600111222" \ -F "campaign=Retencion"import osimport requests
BASE_URL = "https://app.uspeech.io"HEADERS = {"Authorization": f"Api-Key {os.environ['USPEECH_KEY']}"}
with open("call_001.mp3", "rb") as fh: response = requests.post( f"{BASE_URL}/api/files/", headers=HEADERS, data={ "project": 312, "file_type": "audio", "call_datetime": "2026-06-15T09:12:00Z", "agent_id": "A-06", "agent_name": "Ana Diaz", "direction": "inbound", "caller_number": "+34600111222", "campaign": "Retencion", }, files={"file": ("call_001.mp3", fh, "audio/mpeg")}, timeout=300, )response.raise_for_status()import { openAsBlob } from 'node:fs';
const BASE_URL = 'https://app.uspeech.io';const HEADERS = { Authorization: `Api-Key ${process.env.USPEECH_KEY}` };
const form = new FormData();form.set('project', '312');form.set('file_type', 'audio');form.set('call_datetime', '2026-06-15T09:12:00Z');form.set('agent_id', 'A-06');form.set('agent_name', 'Ana Diaz');form.set('direction', 'inbound');form.set('caller_number', '+34600111222');form.set('campaign', 'Retencion');form.set('file', await openAsBlob('./call_001.mp3'), 'call_001.mp3');
const response = await fetch(`${BASE_URL}/api/files/`, { method: 'POST', headers: HEADERS, body: form,});if (!response.ok) throw new Error(await response.text());Consultar los metadatos
Sección titulada «Consultar los metadatos»Todo recurso de archivo —de POST /api/files/, GET /api/files/{id}/ y GET /api/files/?project={id}/— incluye un objeto call_metadata. Es null en los archivos que no tienen registro de llamada, es decir, en todos los archivos de un proyecto que no sea de llamadas.
{ "id": 9871, "original_filename": "call_001.mp3", "status": "transcribed", "call_metadata": { "call_datetime": "2026-06-15T09:12:00Z", "call_datetime_source": "api", "agent_id": "A-06", "agent_name": "Ana Diaz", "agent_source": "api", "direction": "inbound", "caller_number": "+34600111222", "campaign": "Retencion" }}Los dos campos _source indican de dónde procede cada valor, lo que resulta útil al reconciliar con su propio sistema:
| Campo | Valores | Significado |
|---|---|---|
call_datetime_source | api, manifest, upload_time | upload_time significa que nunca se aportó una fecha real de la llamada — se está usando la hora de subida del archivo |
agent_source | api, manifest, llm, "" | llm significa que el nombre del agente se extrajo de la conversación y no lo aportó usted; "" significa que no hay ningún agente vinculado |
PATCH /api/files/{id}/
Sección titulada «PATCH /api/files/{id}/»Actualiza los metadatos de llamada de un archivo ya subido. Solo se modifican los campos que envíe, así que puede cambiar un valor sin repetir el resto.
Content-Type: application/json (o multipart/form-data)
Campos del cuerpo: los mismos seis del endpoint de subida.
Cómo borrar un valor: envíe una cadena vacía. {"campaign": ""} elimina la campaña; enviar agent_id y agent_name vacíos desvincula al agente. Esto se diferencia del manifiesto CSV, donde las celdas en blanco se omiten en lugar de interpretarse como una orden de borrado.
call_datetime es la excepción: no se puede borrar, porque una llamada siempre necesita una hora. Envíe un valor nuevo para cambiarla, u omítala para dejarla intacta; null se rechaza con 400. Si la borra desde la interfaz, se restablece la hora de subida del archivo.
Actualizar los metadatos no vuelve a ejecutar el análisis. Las puntuaciones, los temas, el sentimiento y los resultados de rúbrica quedan intactos.
Respuesta (200 OK): el recurso del archivo actualizado, con el nuevo call_metadata.
Errores:
| Estado | Cuándo |
|---|---|
400 | El proyecto no es de llamadas, el archivo no es una grabación ni una transcripción (solo audio, srt y vtt llevan metadatos de llamada), o algún valor no es válido (p. ej. call_datetime: null, un direction desconocido) |
404 | El archivo no existe o no es visible para quien hace la petición |
Ejemplo — cambiar la campaña y borrar la dirección:
curl -X PATCH https://app.uspeech.io/api/files/9871/ \ -H "Authorization: Api-Key $USPEECH_KEY" \ -H "Content-Type: application/json" \ -d '{"campaign": "Recuperacion", "direction": ""}'response = requests.patch( f"{BASE_URL}/api/files/9871/", headers=HEADERS, json={"campaign": "Recuperacion", "direction": ""}, timeout=30,)response.raise_for_status()print(response.json()["call_metadata"])const response = await fetch(`${BASE_URL}/api/files/9871/`, { method: 'PATCH', headers: { ...HEADERS, 'Content-Type': 'application/json' }, body: JSON.stringify({ campaign: 'Recuperacion', direction: '' }),});if (!response.ok) throw new Error(await response.text());console.log((await response.json()).call_metadata);POST /api/projects/{id}/call-manifest/
Sección titulada «POST /api/projects/{id}/call-manifest/»Sube un manifiesto CSV con metadatos de muchas llamadas a la vez. Las filas se asocian a los archivos del proyecto por nombre de archivo, y las que aún no coinciden con ninguno se guardan en el proyecto y se aplican a los archivos subidos más tarde.
{id} es el ID numérico del proyecto — consulta Proyectos si necesitas
obtenerlo.
Autenticación: clave de API o sesión.
Content-Type: multipart/form-data
Campos del formulario:
| Campo | Obligatorio | Descripción |
|---|---|---|
file | sí | El manifiesto CSV |
Columnas del CSV (se requiere fila de encabezado): filename y call_datetime son obligatorias; agent_id, agent_name, direction, caller_number y campaign son opcionales.
filename,call_datetime,agent_id,agent_name,direction,caller_number,campaigncall_001.mp3,2026-06-15 09:12:00,A-06,Ana Diaz,inbound,+34600111222,Retencioncall_002.mp3,2026-06-15 09:41:00,A-10,Luis Gomez,outbound,+34600333444,RecuperacionFormatos de fecha y hora aceptados: ISO 8601, YYYY-MM-DD HH:MM[:SS] y DD/MM/YYYY HH:MM[:SS]. Los valores sin zona horaria se interpretan en la zona horaria del servidor.
Asociación: primero por nombre exacto, luego por el nombre sin extensión (sin distinguir mayúsculas), de modo que call_001.wav en el manifiesto coincide con un call_001.mp3 ya subido. Se asocian tanto archivos de audio como transcripciones (SRT/VTT).
Respuesta (200 OK):
{ "matched": 118, "unmatched": ["call_119.mp3", "call_120.mp3"], "errors": ["Row 42 (call_041.mp3): invalid call_datetime '15-06-2026'"]}matched— filas aplicadas a un archivo que ya existeunmatched— filas guardadas para archivos que aún no se han subidoerrors— filas omitidas, con el motivo
Errores:
| Estado | Cuándo |
|---|---|
400 | El proyecto no es de llamadas, no se envió file, o el CSV no tenía filas válidas (la respuesta incluye errors) |
404 | El proyecto no existe o no es visible para quien hace la petición |
Ejemplo:
curl -X POST https://app.uspeech.io/api/projects/312/call-manifest/ \ -H "Authorization: Api-Key $USPEECH_KEY" \ -F "file=@./llamadas_junio.csv"with open("llamadas_junio.csv", "rb") as fh: response = requests.post( f"{BASE_URL}/api/projects/312/call-manifest/", headers=HEADERS, files={"file": ("llamadas_junio.csv", fh, "text/csv")}, timeout=120, )response.raise_for_status()result = response.json()print(f"{result['matched']} asociadas, {len(result['unmatched'])} en espera")import { openAsBlob } from 'node:fs';
const form = new FormData();form.set('file', await openAsBlob('./llamadas_junio.csv'), 'llamadas_junio.csv');
const response = await fetch(`${BASE_URL}/api/projects/312/call-manifest/`, { method: 'POST', headers: HEADERS, body: form,});if (!response.ok) throw new Error(await response.text());const result = await response.json();console.log(`${result.matched} asociadas, ${result.unmatched.length} en espera`);Consultar los resultados
Sección titulada «Consultar los resultados»Cuando las llamadas se hayan analizado, los metadatos que adjuntó aquí se convierten en las dimensiones de agrupación y filtrado del informe: agente, dirección, campaña y fecha de la llamada. Consulte la API de Informes de Llamadas para leer esos resultados.