Configurar el cliente
Opciones del cliente del SDK en JavaScript y Python: autenticación, cabeceras automáticas, qué se reintenta y cuánto espera, avisos del servidor y depuración.
Actualizado el . 10 min de lectura
Incluido en todos los planes. Ver precios
El cliente del SDK se crea una vez con la dirección base de la API y una credencial, y a partir de ahí añade solo las cabeceras, reintenta lo que se puede reintentar y convierte cualquier fallo en un UtiliaSDKError. Esta página recoge todas sus opciones, contrastadas con el código de la versión 4.6.0 de los dos paquetes, que está sin publicar; cada opción dice desde qué versión existe.
Opciones
Las opciones son las mismas en los dos lenguajes; cambian el estilo del nombre y la unidad del tiempo de espera.
| JavaScript | Python | Por defecto | Para qué sirve |
|---|---|---|---|
baseURL | base_url | Obligatoria | Dirección base de la API: https://os.utilia.ai/api. |
apiKey | api_key | Sin valor | Clave de la aplicación externa (utl_...) para integraciones de servidor a servidor. |
oauth | oauth | Sin valor | Configuración OAuth 2.1 con PKCE, alternativa a la clave. |
timeout | timeout | 30000 ms / 30.0 s | Tiempo máximo de espera de cada petición. En JavaScript va en milisegundos; en Python, en segundos. |
retryAttempts | retry_attempts | 3 | Número total de intentos de cada petición que se puede repetir, incluido el primero. 1 desactiva los reintentos. Ver Reintentos. |
maxRetryAfterMs | max_retry_after | 60000 ms / 60.0 s | Espera máxima que el SDK acepta cuando el servidor pide esperar (4.6.0). Si pide más, no reintenta: lanza el error con lo que pidió. |
debug | debug | false / False | Activa las trazas de depuración. |
onApiKeyExpires | on_api_key_expires | Sin valor | Función que recibe el aviso de que la clave en uso es la anterior de una renovación (4.2.0). Ver Aviso de que la clave en uso es la anterior. |
onIgnoredFields | on_ignored_fields | Sin valor | Función que recibe los campos que el servidor descartó sin error en una escritura (4.6.0). Ver Campos que el servidor descarta. |
No hay más opciones: el cliente no tiene identificación automática de usuarios. Si tu aplicación trabaja con usuarios externos, identifícalos de forma explícita con identify antes de operar sobre ellos.
En Python, base_url y api_key pueden ir por posición o por nombre; el resto, solo por nombre.
import { UtiliaSDK } from '@utilia-os/sdk-js';
const sdk = new UtiliaSDK({
baseURL: 'https://os.utilia.ai/api',
apiKey: process.env.UTILIA_API_KEY!,
timeout: 15000,
retryAttempts: 5,
debug: true,
});Autenticación: clave u OAuth, nunca las dos
Tienes que dar una credencial y solo una. Si no das ninguna, o das las dos, el cliente no se crea: en JavaScript lanza un UtiliaSDKError con código VALIDATION_ERROR y en Python, un ValueError.
| Clave de acceso | OAuth 2.1 con PKCE | |
|---|---|---|
| Para qué | Integraciones de servidor a servidor | Aplicaciones que actúan en nombre de una persona |
| Qué configuras | Un valor: la clave utl_... | Identificador de cliente, dirección de vuelta y scopes |
| Permisos | Los scopes concedidos a la aplicación externa | Solo los scopes que la persona aceptó |
| Cabecera que envía el SDK | X-Api-Key | Authorization: Bearer <token de acceso> |
| Caducidad | No caduca | El token de acceso caduca y el SDK lo renueva solo |
Con OAuth, la configuración lleva clientId y redirectUri (obligatorios), y opcionalmente clientSecret (solo aplicaciones confidenciales), scopes (por defecto openid, profile y email) y tokenStorage para guardar los tokens donde quieras. En Python los mismos campos van en snake_case y puedes pasarlos como diccionario o como instancia de OAuthConfig.
const sdk = new UtiliaSDK({
baseURL: 'https://os.utilia.ai/api',
oauth: {
clientId: 'client_xxxxxxxxxx',
redirectUri: 'http://localhost:3000/callback',
scopes: ['openid', 'profile', 'email'],
},
});El flujo completo (dirección de autorización, vuelta, almacenamiento y renovación de tokens) está en OAuth con el SDK.
Cabeceras automáticas
| Cabecera | Valor | Cuándo |
|---|---|---|
Content-Type | application/json | Siempre, salvo en las subidas de archivos, que van como multipart/form-data. |
X-Api-Key | Tu clave | Solo con autenticación por clave. |
Authorization | Bearer <token de acceso> | Solo con OAuth. |
x-request-id | Un UUID nuevo por petición | Siempre. |
X-Utilia-SDK | utilia-sdk-js/<versión> o utilia-sdk-python/<versión>, por ejemplo utilia-sdk-js/4.6.0 | Desde la 4.2.0. En JavaScript, en las peticiones a la API, salvo las conexiones en tiempo real (EventSource no admite cabeceras) y las del inicio de sesión OAuth. En Python, también en las conexiones en tiempo real y las descargas de PDF. |
X-Utilia-SDK dice qué SDK y qué versión hace la llamada; las constantes SDK_VERSION, SDK_IDENTITY y SDK_IDENTITY_HEADER (en Python, __version__, SDK_IDENTITY y SDK_IDENTITY_HEADER) exponen esos valores.
El x-request-id sirve para relacionar un fallo de tu lado con los registros del servidor. Cuando la petición falla, el error lo trae en requestId (JavaScript) o request_id (Python): en Python es el valor que devuelve el servidor en su respuesta; en JavaScript, ese mismo o, si el servidor no lo devuelve, el que generó el SDK.
Reintentos
El SDK reintenta solo lo que tiene sentido repetir:
- Errores de red: sin respuesta del servidor (tiempo agotado, conexión rechazada o cortada).
- HTTP 429: límite de peticiones superado. También un 403 cuyo mensaje dice «rate limit», que el SDK trata igual.
- HTTP 5xx: errores del servidor.
No reintenta los 400 y 422 (datos no válidos), 401 (credencial no válida), 403 (acceso denegado o bloqueo por plan), 404 (no encontrado) ni 409 (choca con el estado del recurso): repetirlos daría el mismo resultado.
Qué peticiones se repiten
Desde la 4.6.0, solo se repite lo que no puede duplicar nada:
- Sí: las lecturas,
PUT,PATCHyDELETE; unPOSTcon clave de idempotencia (el campoidempotencyKeydel cuerpo y, en JavaScript, también la cabeceraIdempotency-Key); y losPOSTque solo consultan, comoinvoices.validate,users.identify, el pase de conexión,diagnostics.checkRequest,diagnostics.runy el carril MCP. - No: un
POSTsin clave de idempotencia (tickets, mensajes, clientes, leads, conversión de un lead, canales, agentes, subidas de archivos e IA). Si el corte llega después de que el servidor registre la petición, repetirla la duplicaría: si falla por la red, comprueba si se creó antes de volver a lanzarla.
Hasta la 4.5, esas altas se repetían como cualquier otra petición. Si tu código daba por hecho que el SDK las repetía, ahora te llega el error a la primera.
Algunas escrituras no se repiten nunca
Las operaciones que no se pueden repetir sin riesgo de duplicar algo hacen un solo intento aunque lleven clave de idempotencia y configures más: emitir o anular una factura, enviarla, cobrarla sin el usuario delante, registrar un pago o una devolución, programar o reintentar un cobro, registrar una autorización de cobro y algunas operaciones de suscripciones. Si fallan por la red, repítelas tú con la misma clave de idempotencia, o comprueba el estado antes de volver a lanzarlas. El cobro con el usuario delante (payInvoice) sí sigue la política general de reintentos.
Cuánto espera entre intentos
Si el servidor dice cuánto esperar, el SDK le hace caso: en un 429, o en un 503 que la traiga, espera lo que pide la cabecera Retry-After. X-Utilia-Quota-Reset solo cuenta como espera en el 429 del cupo por hora agotado (RATE_LIMIT_EXCEEDED sin llamadas restantes) que no traiga Retry-After: el servidor manda esa cabecera en todas las respuestas, y en un 5xx o en otro 429 no tiene nada que ver con cuánto esperar, así que esos se repiten con la espera creciente de siempre. Si la espera pasa de maxRetryAfterMs (max_retry_after), que por defecto es un minuto, no espera ni reintenta: lanza el error con retryAfterMs (retry_after, en segundos), para que decidas tú si lo dejas para más tarde. Es lo que pasa cuando se agota el cupo por hora de la clave.
Si el servidor no dice nada, espera cada vez el doble (0,5 s, 1 s, 2 s, 4 s…). Con el valor por defecto de retryAttempts, 3, hace un intento y hasta dos reintentos:
Reintentos sin indicación del servidor
0 s
Intento 1
Falla por la red o con un 5xx.Espera de 0,5 s hasta el siguiente.
0,5 s
Intento 2
Vuelve a fallar por lo mismo.Espera de 1 s hasta el siguiente.
1,5 s
Intento 3
Si también falla, el SDK lanza el UtiliaSDKError del último intento.
Con OAuth, un 401 provoca además una renovación automática del token y un nuevo intento con el token renovado. Si la renovación falla, el error llega tal cual. Desde la 4.2.0 hay una excepción: un 401 OAUTH_BEARER_NOT_ALLOWED no renueva el token ni repite la petición, porque esa ruta exige la sesión de una persona y renovar no cambia nada.
Depuración
Con debug activado, el SDK deja una traza por petición y otra por respuesta:
- JavaScript escribe en la consola con el prefijo
[UtiliaSDK]:Petición: POST /external/v1/tickets,Respuesta: 201 /external/v1/ticketsy, si reintenta,Reintento 1/2, esperando 500ms. - Python usa el registro estándar con el nombre
utilia_sdky nivelDEBUG:POST /external/v1/tickets (intento 1)yRespuesta: 201 /external/v1/tickets. No imprime nada hasta que configuras el registro de tu aplicación, por ejemplo conlogging.getLogger("utilia_sdk").setLevel(logging.DEBUG).
No dejes la depuración activada en producción: las trazas muestran las rutas que llamas.
Aviso de que la clave en uso es la anterior
Cuando alguien renueva la clave de tu aplicación con un periodo de transición, la anterior sigue valiendo unos días y cada respuesta a una petición hecha con ella trae la cabecera X-Utilia-Api-Key-Expires con la fecha en que dejará de valer. Desde la 4.2.0, el SDK la lee por ti:
- Si configuras
onApiKeyExpires(on_api_key_expiresen Python), la llama una vez por fecha con un aviso que traeexpiresAt(expires_at). - Si no la configuras, deja un único aviso por fecha: en JavaScript, en la consola; en Python, en el registro
utilia_sdkcon nivelWARNING. sdk.apiKeyExpiresAt(sdk.api_key_expires_at) devuelve esa fecha, onull(None) si el servidor no ha avisado.
El aviso es solo informativo: nunca cambia el resultado de la petición, y si tu función falla, el SDK lo ignora. Úsalo para saber que algún servidor tuyo todavía no tiene la clave nueva.
const sdk = new UtiliaSDK({
baseURL: 'https://os.utilia.ai/api',
apiKey: process.env.UTILIA_API_KEY!,
onApiKeyExpires: ({ expiresAt }) => {
alertas.enviar(`La clave de UTILIA OS de este servidor deja de valer el ${expiresAt}`);
},
});Cómo se renueva la clave y cuánto dura la transición está en Autenticación con clave de acceso.
Campos que el servidor descarta
Una escritura puede salir bien y aun así no guardar un dato: si mandas un campo que la ruta no admite, casi siempre un nombre mal escrito, el servidor lo descarta sin error y lo dice en la cabecera X-Utilia-Ignored-Fields. Desde la 4.6.0, el SDK la lee por ti:
- Si configuras
onIgnoredFields(on_ignored_fields), la llama una vez por método, ruta y campos con un aviso que traemethod,path,fields(los nombres, nunca los valores) yrequestId(request_id). - Si no la configuras, deja un aviso: en JavaScript, en la consola; en Python, en el registro
utilia_sdkcon nivelWARNING.
Como el otro aviso, nunca cambia el resultado de la petición. Para saber antes de enviarla qué campos se descartarían, usa diagnostics.checkRequest.
const sdk = new UtiliaSDK({
baseURL: 'https://os.utilia.ai/api',
apiKey: process.env.UTILIA_API_KEY!,
onIgnoredFields: ({ method, path, fields, requestId }) => {
registro.warn(`${method} ${path} descartó ${fields.join(', ')} (petición ${requestId})`);
},
});Conexiones en tiempo real
Las funciones que reciben eventos en tiempo real (por ejemplo, el seguimiento de sugerencias con IA) usan Server-Sent Events. Desde la 4.2.0, la clave no viaja nunca en la dirección de la conexión: en JavaScript, como EventSource no admite cabeceras propias, el SDK pide antes un pase de conexión de un solo uso y abre la conexión con él; en Python, la clave va en la cabecera X-Api-Key. Lo hace por dentro: no tienes que construir esa dirección tú. Si necesitas abrir una conexión por tu cuenta, pide el pase con createStreamToken.
Si tu código usaba buildSseUrl o buildSseUrlAsync del cliente de JavaScript, ten en cuenta que están en desuso y ya no ponen la clave en ninguna dirección: su sustituto es buildUrl(path), que devuelve la dirección sin credenciales. Para las conexiones de tickets, usa el url del pase.
Diferencias entre los dos lenguajes
| Aspecto | JavaScript | Python |
|---|---|---|
| Nombres | camelCase (baseURL, apiKey) | snake_case (base_url, api_key) |
| Tiempo de espera | Milisegundos | Segundos |
| Cliente HTTP | axios | httpx |
| Modelos | Interfaces de TypeScript | Modelos de Pydantic |
| Modo síncrono | No aplica | UtiliaSDKSync |
| Cerrar la sesión | No hace falta | async with / with, o close() |
| Comprobaciones del error | Métodos: error.isRateLimited() | Propiedades: error.is_rate_limited |