TAuth API
Códigos de un solo uso (OTP) por SMS y WhatsApp: TAuth genera el PIN, lo entrega por el canal pedido (con fallback automático a otro canal) y lo verifica.
URL base:
https://api-tauth.ti.t-mobs.com/v1/
- Requests y respuestas en JSON (
Content-Type: application/json), UTF-8. - Las fechas son ISO 8601 en UTC, por ejemplo
2026-10-28T14:03:21.512000Z. - Siempre por HTTPS.
Autenticación
Cada request lleva la API key del cliente, que entrega T-mob, en el header Authorization:
Authorization: Token 3f2a9c1e-5b7d-4e8a-9f10-2c3d4e5f6a7b
Sin el header, o con una key inválida o desactivada, la respuesta es 403.
Flujo
- Enviar el OTP al número del usuario con
POST /v1/send-otp/. La respuesta trae untransaction: guardarlo asociado al usuario. - El usuario recibe el PIN por WhatsApp o SMS y lo ingresa en tu app.
- Verificar el PIN con
POST /v1/verify-otp/{transaction}/. Siotp_statusesVerified, el número está validado. - Opcional: mostrar el estado de la entrega con
GET /v1/otp/{transaction}/, ofrecer un botón "reenviar" conPOST /v1/otp/{transaction}/resend/o recibir los eventos por webhook.
Enviar un OTP
Body
| Campo | Tipo | Descripción | |
|---|---|---|---|
msisdn | string | obligatorio | Número de destino en formato internacional, con el código de país:
+ opcional y de 6 a 15 dígitos (+5491112345678). Los espacios se ignoran.
El país de destino se toma del código de país y tiene que estar habilitado para tu cuenta. |
channel | string | opcional | whatsapp o sms. Sin este campo se usa el canal principal configurado para tu cuenta
que esté disponible en el país del número. |
fallback | string | opcional | sms o whatsapp. Si el canal principal no entrega el mensaje, se reenvía el
mismo PIN por este canal. Si el canal principal no está disponible en el país del número, se usa
directamente este. Ver Canales y fallback. |
template | string | opcional | Template del mensaje configurado para tu cuenta (texto, largo del PIN, vencimiento). Sin este campo se usa el template por defecto. |
country | string | opcional | Código ISO de 2 letras (AR). Si se envía, tiene que coincidir con el país del
msisdn (control adicional). |
Headers opcionales
| Header | Descripción |
|---|---|
Idempotency-Key | Identificador único del pedido (hasta 100 caracteres). Si se repite el request con la misma key, se devuelve la misma respuesta sin mandar otro OTP. Ver Idempotencia. |
Ejemplo
curl -X POST https://api-tauth.ti.t-mobs.com/v1/send-otp/ \
-H "Authorization: Token <api_key>" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: signup-8f14e45f" \
-d '{"msisdn": "+5491112345678", "channel": "whatsapp", "fallback": "sms"}'
Respuesta 202 Accepted
{
"transaction": "0b6e3c55-3d7e-4a43-9d61-5f0d6f8f2f1a",
"message": {
"status": "Accepted",
"description": "Accepted for Delivery",
"channel": "whatsapp",
"updated_at": "2026-10-28T14:03:21.512000Z"
},
"otp": {
"otp_status": "Not verified",
"expires_at": "2026-10-28T14:04:21.512000Z"
}
}
El envío es asincrónico: 202 significa que el OTP fue aceptado y está en camino.
Errores
| Código | error | Causa |
|---|---|---|
| 400 | Invalid JSON. | El body no es un objeto JSON. |
| 400 | Invalid parameters. | Falta el msisdn o no tiene un formato válido. |
| 400 | Invalid channel. | channel o fallback no son sms ni whatsapp. |
| 400 | Country not enabled. | El país del número no está habilitado para tu cuenta (o el número no tiene código de país). |
| 400 | Country not available for channel. | El canal pedido no está disponible para ese país y no se mandó un fallback disponible. |
| 400 | Invalid template. | El template no existe o está inactivo. |
| 400 | Invalid msisdn. | El número no respeta el formato de su país, o no coincide con country. |
| 400 | Invalid Idempotency-Key. | La key está vacía, es muy larga o tiene caracteres no imprimibles. |
| 402 | Monthly spend limit reached. | Se alcanzó el tope de gasto mensual de tu cuenta. |
| 409 | Request in progress. | Otro request con la misma Idempotency-Key se está procesando. |
| 422 | Idempotency-Key already used with another body. | La key ya se usó con otros datos. |
| 429 | MSISDN rate limit exceeded. | Demasiados OTP a ese número. Ver Límites. |
| 429 | Client rate limit exceeded. | Demasiados OTP por minuto en tu cuenta. |
Verificar un OTP
curl -X POST https://api-tauth.ti.t-mobs.com/v1/verify-otp/0b6e3c55-3d7e-4a43-9d61-5f0d6f8f2f1a/ \
-H "Authorization: Token <api_key>" \
-H "Content-Type: application/json" \
-d '{"otp": "482193"}'
Respuesta 200 OK
{
"transaction": "0b6e3c55-3d7e-4a43-9d61-5f0d6f8f2f1a",
"message": {
"status": "Delivered",
"description": "OTP reached final destination",
"channel": "whatsapp",
"updated_at": "2026-10-28T14:03:24.101000Z"
},
"otp": {
"otp_status": "Verified",
"expires_at": "2026-10-28T14:04:21.512000Z",
"remaining_attempts": 5
}
}
Una respuesta 200 no implica que el PIN sea correcto: hay que mirar otp.otp_status.
otp_status | Significado |
|---|---|
| Verified | PIN correcto. El OTP se consume: un segundo intento responde 404. |
| Not verified | PIN incorrecto. Se puede volver a intentar mientras queden remaining_attempts. |
| Blocked | Se agotaron los intentos. El OTP ya no se puede verificar: hay que pedir uno nuevo. |
| Expired | Pasó expires_at. Hay que pedir uno nuevo (o reenviarlo antes de que venza). |
Errores
| Código | error | Causa |
|---|---|---|
| 400 | Invalid JSON. | El body no es un objeto JSON. |
| 400 | Invalid parameters. | Falta otp o no es un string. |
| 404 | Invalid transaction. | La transacción no existe, es de otra cuenta, ya fue verificada o venció hace más de 24 horas. |
Consultar el estado
Devuelve el estado de la entrega y del OTP, con el mismo formato que la verificación (sin el PIN). Sirve para mostrar en tu app "enviado por WhatsApp", "entregado", etc.
curl https://api-tauth.ti.t-mobs.com/v1/otp/0b6e3c55-3d7e-4a43-9d61-5f0d6f8f2f1a/ \
-H "Authorization: Token <api_key>"
Una transacción ya verificada responde otp_status: "Verified". Una inexistente o de otra cuenta, 404.
Reenviar
Reenvía el mismo PIN (no hace falta body). Si hay otro proveedor u otro canal disponible, usa el siguiente; si no, el mismo. El vencimiento se reinicia.
curl -X POST https://api-tauth.ti.t-mobs.com/v1/otp/0b6e3c55-3d7e-4a43-9d61-5f0d6f8f2f1a/resend/ \
-H "Authorization: Token <api_key>"
Responde 202 con el estado actualizado y otp.remaining_resends.
| Código | error | Causa |
|---|---|---|
| 403 | Resend not enabled. | Tu cuenta no tiene reenvíos habilitados. |
| 404 | Invalid transaction. | No existe, es de otra cuenta o ya fue verificada. |
| 409 | OTP expired. / OTP blocked. | El OTP venció o se agotaron los intentos: hay que pedir uno nuevo. |
| 429 | Resend too soon. | No pasó el tiempo mínimo desde el último envío. |
| 429 | Resend limit exceeded. | Se alcanzó la cantidad máxima de reenvíos. |
Formato por país
Con la IP pública del usuario devuelve el formato de número de su país, para prellenar el prefijo o validar el número antes de enviar.
curl "https://api-tauth.ti.t-mobs.com/v1/info/?ip=181.30.12.4" -H "Authorization: Token <api_key>"
{
"results": {
"format": "+54 9 XX XXXX-XXXX",
"current_country": "AR",
"current_regexp": "\\+549[0-9]{10}",
"current_prefix": "54"
}
}
400 Invalid parameters. si la IP no es pública; 400 Country not available. si el país no está habilitado.
Consumo
Tu consumo por día y por país, con el importe que se factura. Sin parámetros devuelve el mes en curso; el rango máximo es de 92 días. Los datos del día actual se actualizan cada 30 minutos.
curl "https://api-tauth.ti.t-mobs.com/v1/usage/?from=2026-10-01&to=2026-10-31" -H "Authorization: Token <api_key>"
{
"currency": "USD",
"from": "2026-10-01",
"to": "2026-10-31",
"days": [
{"date": "2026-10-28", "country": "AR", "otps": 120, "verified": 101, "not_verified": 19,
"blocked": 2, "expired": 5, "abandoned": 12,
"sms_sent": 30, "sms_delivered": 28, "whatsapp_sent": 118, "whatsapp_delivered": 112,
"rejected": 3, "rejections": {"invalid_msisdn": 2, "msisdn_rate_limit": 1},
"amount": "5.0400"}
],
"totals": {"otps": 120, "verified": 101, "not_verified": 19, "blocked": 2, "expired": 5,
"abandoned": 12, "sms_sent": 30, "sms_delivered": 28,
"whatsapp_sent": 118, "whatsapp_delivered": 112, "rejected": 3, "amount": "5.0400"}
}
not_verified se divide en blocked (se agotaron los intentos), expired (se ingresaron PIN incorrectos y venció) y abandoned (nunca se ingresó un PIN). Los números del día actual son provisorios: un OTP vigente cuenta como no verificado hasta que se verifica.
rejected cuenta los send-otp rechazados del día y rejections los separa por motivo: invalid_json, invalid_msisdn, invalid_channel, country_not_enabled, channel_not_available, invalid_template, spend_limit, client_rate_limit y msisdn_rate_limit. Sirve para detectar errores de integración. Los rechazos de números sin país reconocido van con "country": null.
Se factura cada mensaje enviado (incluidos los fallbacks y los reenvíos), al precio de tu cuenta para ese país y canal. Los días se cuentan en hora de Argentina (UTC-3).
Estados
Entrega del mensaje (message.status)
| Estado | Significado |
|---|---|
| Accepted | Aceptado, en cola para enviar. |
| In progress | Enviado al operador, sin confirmación de entrega todavía. Algunos destinos no confirman: el mensaje puede llegar igual. |
| Delivered | El operador confirmó la entrega. |
| Failed | No se pudo entregar por ningún canal disponible. |
message.channel es el canal del último envío: si hubo fallback, cambia (por ejemplo de whatsapp a sms).
OTP (otp.otp_status)
Not verified, Verified, Blocked o Expired. Ver Verificar un OTP.
Canales y fallback
Con channel se elige el canal principal y con fallback el canal alternativo. Si el
canal principal no entrega, TAuth reenvía el mismo PIN en la misma transacción por el
canal alternativo y reinicia el vencimiento. No hay que hacer nada: el usuario usa el PIN que le llegue.
El fallback se activa cuando:
- el envío falla,
- el operador informa que el mensaje no se pudo entregar, o
- pasa el tiempo configurado para tu cuenta sin que el PIN se verifique y sin confirmación de entrega.
Si se agotan todas las opciones, message.status pasa a Failed.
Cada canal tiene que estar habilitado para el país del número. Si el canal pedido no lo está y se mandó
fallback, el OTP sale directamente por el canal alternativo (la respuesta lo indica en
message.channel). Cada mensaje enviado, incluidos los fallbacks, se factura al precio de su canal.
Webhooks
Si T-mob configura una URL de webhook para tu cuenta, TAuth le hace un POST JSON por cada evento:
| Evento | Cuándo | Campos extra |
|---|---|---|
message.delivered | El operador confirmó la entrega. | channel |
message.fallback | Se reenvió el PIN por otro proveedor o canal (fallback o reenvío pedido). | reason, step |
message.failed | No quedan canales para entregar. | reason |
otp.verified | El PIN se verificó. | |
otp.blocked | Se agotaron los intentos de verificación. | |
billing.spend_alert | El gasto del mes llegó al porcentaje de aviso del tope (una vez por mes). Sin transaction. | month, spent, limit, currency, percent |
billing.spend_limit_reached | El gasto del mes llegó al tope: desde ahora send-otp responde 402 (una vez por mes). | ídem |
POST /tu/webhook HTTP/1.1
Content-Type: application/json
X-TAuth-Event: message.delivered
X-TAuth-Timestamp: 1793196204
X-TAuth-Signature: sha256=5d41402abc4b2a76b9719d911017c592...
{"event":"message.delivered","transaction":"0b6e3c55-3d7e-4a43-9d61-5f0d6f8f2f1a","created_at":"2026-10-28T14:03:24.101000Z","channel":"whatsapp"}
Verificar la firma
X-TAuth-Signature es sha256= + el HMAC-SHA256, en hexadecimal, de
{timestamp}.{body}, donde la clave es el secreto del webhook (lo entrega T-mob). Calcularlo sobre el body
tal cual llega (sin parsear) y rechazar timestamps de más de 5 minutos.
# Python
import hashlib, hmac, time
def valid(headers, raw_body: bytes, secret: str) -> bool:
ts = headers["X-TAuth-Timestamp"]
expected = "sha256=" + hmac.new(secret.encode(), ts.encode() + b"." + raw_body,
hashlib.sha256).hexdigest()
return (hmac.compare_digest(expected, headers["X-TAuth-Signature"])
and abs(time.time() - int(ts)) < 300)
// Node.js
const crypto = require("crypto");
function valid(headers, rawBody, secret) {
const ts = headers["x-tauth-timestamp"];
const expected = "sha256=" + crypto.createHmac("sha256", secret)
.update(ts + "." + rawBody).digest("hex");
const got = headers["x-tauth-signature"] || "";
return got.length === expected.length
&& crypto.timingSafeEqual(Buffer.from(got), Buffer.from(expected))
&& Math.abs(Date.now() / 1000 - Number(ts)) < 300;
}
Entrega
- Responder un
2xxen menos de 5 segundos. Si el procesamiento demora, conviene encolarlo. - Ante un error o timeout, TAuth reintenta hasta 6 veces con espera creciente (10 s, 20 s, 40 s…, unos 10 minutos en total).
- Un evento puede llegar más de una vez o fuera de orden: procesarlo de forma idempotente (por ejemplo, por
transaction+event).
Idempotencia
Si un send-otp da timeout o error de red, no se sabe si el OTP salió. Para reintentar sin
mandar dos mensajes, se usa el header Idempotency-Key con un valor único por pedido (por ejemplo un UUID):
- Mismo key y mismo body dentro de las 24 horas: se devuelve la respuesta original con el header
Idempotent-Replayed: true. - Mismo key con otro body:
422. - Si el primer request todavía se está procesando:
409. Reintentar en unos segundos. - Las respuestas con error no se guardan: se puede corregir y reintentar con la misma key.
Errores
Los errores responden JSON con el campo error:
{"error": "Invalid template."}
| Código | Significado |
|---|---|
| 400 | Request inválido. |
| 402 | Se alcanzó el tope de gasto mensual de la cuenta. |
| 403 | API key ausente o inválida, o función no habilitada. |
| 404 | Transacción o ruta inexistente. |
| 405 | Método HTTP no permitido para la ruta (sin body). |
| 409 | Conflicto de estado (OTP vencido o bloqueado, request en curso). |
| 422 | Idempotency-Key reutilizada con otro body. |
| 429 | Se superó un límite. Reintentar más tarde. |
| 5xx | Error de TAuth. Reintentar con backoff (y con Idempotency-Key en send-otp). |
Límites
Los configura T-mob para cada cuenta. Valores por defecto:
| Límite | Por defecto |
|---|---|
| Intentos de verificación por OTP | 5 |
| OTP por número | 5 por hora |
| OTP por minuto en la cuenta | sin límite |
| Reenvíos por OTP | 3, con al menos 30 segundos entre envíos |
| Vencimiento del PIN | según el template (por defecto 60 segundos), se reinicia con cada reenvío |
| Gasto mensual | sin tope, salvo que se acuerde uno (al alcanzarlo, send-otp responde 402) |
Ejemplo completo
# Python 3, con requests
import uuid
import requests
BASE = "https://api-tauth.ti.t-mobs.com/v1"
AUTH = {"Authorization": "Token <api_key>"}
# 1. Enviar
r = requests.post(f"{BASE}/send-otp/",
json={"msisdn": "+5491112345678", "channel": "whatsapp", "fallback": "sms"},
headers={**AUTH, "Idempotency-Key": str(uuid.uuid4())}, timeout=10)
r.raise_for_status()
transaction = r.json()["transaction"]
# 2. El usuario ingresa el PIN que recibió
pin = input("PIN: ")
# 3. Verificar
r = requests.post(f"{BASE}/verify-otp/{transaction}/", json={"otp": pin},
headers=AUTH, timeout=10)
if r.status_code == 200 and r.json()["otp"]["otp_status"] == "Verified":
print("Número verificado")
else:
print("No verificado:", r.json())