Guía de la API de SMSCode para Desarrolladores: Integración Paso a Paso

Guía de la API de SMSCode para Desarrolladores: Integración Paso a Paso

Si estás construyendo algo que involucra verificación por teléfono — registro de usuarios, pruebas automatizadas de flujos de onboarding, aprovisionamiento de cuentas en volumen, testing de integraciones, o pipelines de QA — hacer todo esto a mano a través de un dashboard se vuelve insostenible rápidamente. A la décima verificación manual ya es un cuello de botella. A la centésima, es imposible.

La API REST de SMSCode te permite automatizar el ciclo completo desde tu backend: solicitar un número virtual, esperar el SMS, extraer el código OTP y continuar con el flujo. Todo sin clics, sin intervención manual, directamente integrado en tu sistema.

Esta guía cubre autenticación, los endpoints principales con sus parámetros completos, ejemplos funcionales en curl y Python, los errores más comunes que vas a encontrar, y las buenas prácticas que separan una integración robusta de una que falla en producción.

TL;DR: La API V1 de SMSCode usa autenticación Bearer token. El flujo principal es: verificar saldo → consultar catálogo → crear pedido → hacer polling del OTP → completar o cancelar. Todas las respuestas siguen la forma { success, data } o { success, error }. Cachea las llamadas al catálogo y haz polling cada 5 segundos para un uso eficiente que no consuma el rate limit.

Autenticación

Cada llamada a la API V1 necesita un Bearer token en el header Authorization. Lo encontrás en Configuración de cuenta dentro del dashboard.

Authorization: Bearer TU_API_TOKEN

Regla de oro para el token: Guardalo exclusivamente del lado del servidor. No lo incluyas en JavaScript del cliente, no lo pongas en archivos .env commiteados a git, no lo expongas en logs de aplicación. Si un token se compromete, regénéralo inmediatamente desde la configuración de tu cuenta — cualquier token anterior queda inválido instantáneamente.

La referencia completa de la API está en la documentación. Si aún no tenés cuenta, registrate aquí — necesitarás saldo para hacer pedidos reales.

Paso 1: Verificar tu saldo

Antes de hacer cualquier pedido, verificá que tu cuenta tiene crédito suficiente para la verificación que necesitás:

curl -X GET https://api.smscode.gg/v1/balance \
  -H "Authorization: Bearer TU_API_TOKEN"

Respuesta exitosa:

{
  "success": true,
  "data": {
    "currency": "IDR",
    "balance": 150000
  }
}

El saldo está denominado en IDR (Rupias indonesias). Si está en cero o es insuficiente para el producto que querés, recargá desde la página de precios antes de continuar. Los precios de productos también están denominados en IDR en la API — podés ver los equivalentes en USD en el dashboard web.

Equivalente en Python:

import requests

API_TOKEN = "TU_API_TOKEN"
BASE_URL = "https://api.smscode.gg/v1"
headers = {"Authorization": f"Bearer {API_TOKEN}"}

resp = requests.get(f"{BASE_URL}/balance", headers=headers)
resp.raise_for_status()
data = resp.json()
print(f"Saldo: {data['data']['balance']} {data['data']['currency']}")

El endpoint de catálogo devuelve los productos de números virtuales disponibles, filtrables por país y plataforma. Este es el endpoint donde encontrás el product_id que vas a usar para crear el pedido.

curl -X GET "https://api.smscode.gg/v1/catalog/products?country=mexico&platform=whatsapp" \
  -H "Authorization: Bearer TU_API_TOKEN"

La respuesta incluye IDs de producto, precios en IDR, niveles de stock disponibles, y metadatos del número. Podés filtrar por cualquier combinación de país y plataforma. El catálogo visual en el sitio web te muestra los mismos datos si querés explorar antes de escribir código.

Consejo crítico sobre el cacheo: No consultes el catálogo en cada pedido dentro de un loop de verificaciones. Los productos no cambian segundo a segundo. Cachéalo por al menos 60 segundos en producción. En sistemas de alta frecuencia, una caché de 5 minutos es perfectamente válida. Esto respeta los rate limits y reduce la latencia de tu sistema.

Equivalente en Python con selección del producto más económico:

params = {"country": "mexico", "platform": "whatsapp"}
resp = requests.get(f"{BASE_URL}/catalog/products", headers=headers, params=params)
resp.raise_for_status()
products = resp.json()["data"]

if not products:
    raise ValueError("No hay productos disponibles para este país y plataforma")

# Elegí el producto de menor precio con stock disponible
product = min(products, key=lambda p: p["price"])
product_id = product["id"]
print(f"Producto seleccionado: {product_id} a {product['price']} IDR")

Paso 3: Crear un pedido

Una vez que tenés un product_id, creás el pedido para rentar el número:

curl -X POST https://api.smscode.gg/v1/orders \
  -H "Authorization: Bearer TU_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"product_id": 42}'

Respuesta exitosa:

{
  "success": true,
  "data": {
    "order_id": "ord_abc123",
    "phone_number": "+5215512345678",
    "status": "WAITING",
    "expires_at": "2026-03-16T12:05:00Z"
  }
}

El phone_number es el número que ingresás en la app o sitio web que estás verificando. El estado del pedido comienza como WAITING — eso es el comportamiento correcto. Ahora activá el proceso de envío del OTP desde el servicio destino usando ese número.

Equivalente en Python:

resp = requests.post(
    f"{BASE_URL}/orders",
    headers={**headers, "Content-Type": "application/json"},
    json={"product_id": product_id}
)
resp.raise_for_status()
order = resp.json()["data"]
order_id = order["order_id"]
phone_number = order["phone_number"]
expires_at = order["expires_at"]
print(f"Número asignado: {phone_number}")
print(f"Válido hasta: {expires_at}")

Importante: Guardá el order_id — lo vas a necesitar para el polling y para cancelar si fuera necesario. Y tené en cuenta el expires_at — si tu sistema tarda demasiado en activar el OTP, el pedido puede expirar antes de que el SMS llegue.

Paso 4: Hacer polling del OTP

Consultá el estado del pedido repetidamente hasta que llegue el SMS o hasta que el pedido expire:

curl -X GET https://api.smscode.gg/v1/orders/ord_abc123 \
  -H "Authorization: Bearer TU_API_TOKEN"

Respuesta cuando llega el SMS:

{
  "success": true,
  "data": {
    "order_id": "ord_abc123",
    "status": "OTP_RECEIVED",
    "otp_code": "847291",
    "phone_number": "+5215512345678"
  }
}

Estados posibles del campo status:

EstadoSignificadoAcción recomendada
WAITINGNúmero activado, sin SMS todavíaContinuar polling
OTP_RECEIVEDCódigo disponible en otp_codeUsar el código
COMPLETEDPedido finalizado exitosamenteNo se requiere acción
CANCELLEDPedido cancelado, reembolso emitidoCrear nuevo pedido si necesario
EXPIREDVentana cerrada sin SMSCrear nuevo pedido

Loop de polling completo en Python:

import time

max_wait = 120   # segundos máximos de espera
interval = 5     # consultar cada 5 segundos
elapsed = 0
otp = None

while elapsed < max_wait:
    resp = requests.get(f"{BASE_URL}/orders/{order_id}", headers=headers)
    resp.raise_for_status()
    order_data = resp.json()["data"]
    status = order_data["status"]

    if status == "OTP_RECEIVED":
        otp = order_data["otp_code"]
        print(f"OTP recibido: {otp}")
        break
    elif status in ("CANCELLED", "EXPIRED"):
        print(f"Pedido terminó con estado: {status}")
        break
    elif status == "WAITING":
        print(f"Esperando... ({elapsed}s)")

    time.sleep(interval)
    elapsed += interval

if otp is None:
    print("No se recibió OTP en el tiempo esperado")

Intervalos de 5 segundos son el punto óptimo para la mayoría de casos. Hacer polling más frecuente no acelera la entrega del SMS — el tiempo de entrega depende de la red del proveedor, no de la frecuencia de tus consultas. Lo que sí logra el polling agresivo es consumir tu cuota de rate limits más rápido.

Paso 5: Completar o cancelar

El pedido se completa automáticamente cuando el servicio destino recibe y valida el OTP. Si ya no necesitás el número — porque cambiaste de estrategia, la verificación del servicio destino falló por otro motivo, o detectás que el flujo tiene un error — cancelá el pedido:

curl -X POST https://api.smscode.gg/v1/orders/ord_abc123/cancel \
  -H "Authorization: Bearer TU_API_TOKEN"

Política de reembolso:

  • Cancelar antes de que llegue el OTP → reembolso automático del saldo
  • Cancelar después de que llegó el OTP → sin reembolso (la entrega fue exitosa)
  • El pedido expira sin SMS → reembolso automático sin necesidad de cancelar manualmente

Manejo de errores

Todos los errores de la API siguen la misma estructura consistente:

{
  "success": false,
  "error": {
    "code": "INSUFFICIENT_BALANCE",
    "message": "El saldo de la cuenta es insuficiente para este pedido."
  }
}

Códigos de error que vas a encontrar:

CódigoHTTP StatusSignificado y acción
UNAUTHORIZED401Token inválido o ausente — verificá el token
INSUFFICIENT_BALANCE422Saldo insuficiente — recargá antes de continuar
PRODUCT_UNAVAILABLE422Stock agotado — consultá catálogo para siguiente producto
ORDER_NOT_FOUND404ID de pedido incorrecto o de otra cuenta
RATE_LIMITED429Demasiadas solicitudes — reducí la frecuencia

El error PRODUCT_UNAVAILABLE merece atención especial. El stock del catálogo es datos en tiempo real. Entre que consultás el catálogo y hacés el pedido, un producto popular puede agotarse — especialmente en plataformas de alto volumen como WhatsApp o Instagram donde hay mucha demanda simultánea. Tu integración debe manejar este error con un retry automático sobre el siguiente producto disponible en tus resultados del catálogo.

# Manejo correcto de PRODUCT_UNAVAILABLE con fallback
def create_order_with_fallback(products, headers):
    for product in products:
        try:
            resp = requests.post(
                f"{BASE_URL}/orders",
                headers={**headers, "Content-Type": "application/json"},
                json={"product_id": product["id"]}
            )
            data = resp.json()

            if data.get("success"):
                return data["data"]

            if data.get("error", {}).get("code") == "PRODUCT_UNAVAILABLE":
                continue  # Intentar con el siguiente producto

            raise Exception(f"Error inesperado: {data}")
        except requests.HTTPError:
            continue

    raise Exception("No hay productos disponibles en ningún rango de precio")

Rate limits

La API aplica rate limits por cuenta. Superarlos devuelve HTTP 429 con código de error RATE_LIMITED. Los rate limits están diseñados para cubrir cargas de automatización normales — si los estás alcanzando consistentemente, hay un problema de diseño en tu integración, no de capacidad de la API.

Dos prácticas que te mantienen por debajo del limit:

  1. Cachear respuestas del catálogo durante al menos 60 segundos
  2. Hacer polling cada 5 segundos, no bombardear el endpoint de órdenes

Errores comunes de integración que debes evitar

No manejar PRODUCT_UNAVAILABLE. Como se explica arriba, el stock puede vaciarse entre tu llamada al catálogo y tu pedido. Este error ocurrirá en producción — no es un edge case sino un caso esperado.

Polling demasiado agresivo. Consultar el endpoint de órdenes cada segundo no hace que el SMS llegue más rápido. Solo consume tu cuota de rate limits. Cinco segundos es el intervalo correcto.

Hardcodear IDs de producto. Los IDs de producto pueden cambiar sin previo aviso si el proveedor actualiza su inventario. Siempre resolvé el ID dinámicamente desde el catálogo en runtime, filtrando por país y plataforma destino. Nunca escribas un ID de producto como constante en tu código.

Ignorar expires_at. Los pedidos tienen una ventana de verificación finita. Si tu sistema espera demasiado para activar el OTP desde el servicio destino, el pedido expira y perdés el número. Activá el OTP en el servicio destino inmediatamente después de obtener el número — no cuando tengas “tiempo”.

No manejar el estado EXPIRED correctamente. El estado EXPIRED es un resultado normal, no un error catastrófico. Tu sistema debe detectarlo, crear un nuevo pedido con un número diferente, y reintentar el flujo completo.

Mezclar COMPLETED y OTP_RECEIVED en la lógica. En la práctica, debés actuar sobre OTP_RECEIVED — ahí es cuando el código está disponible. COMPLETED llega después y confirma que el ciclo cerró, pero para tu flujo de uso, OTP_RECEIVED es el estado de acción.

Casos de uso avanzados

Verificación en lote con procesamiento asíncrono

Si necesitás verificar cientos de cuentas por día, el flujo asíncrono es significativamente más eficiente que el secuencial. En lugar de esperar a que cada pedido resuelva su OTP antes de crear el siguiente, creás múltiples pedidos en paralelo y hacés polling de todos simultáneamente.

import asyncio
import aiohttp

async def poll_order(session, order_id, headers, max_wait=120):
    """Poll un pedido hasta OTP_RECEIVED o timeout"""
    for _ in range(max_wait // 5):
        async with session.get(
            f"{BASE_URL}/orders/{order_id}",
            headers=headers
        ) as resp:
            data = await resp.json()
            status = data["data"]["status"]

            if status == "OTP_RECEIVED":
                return data["data"]["otp_code"]
            elif status in ("CANCELLED", "EXPIRED"):
                return None

        await asyncio.sleep(5)

    return None

# Crear múltiples pedidos en paralelo y hacer polling simultáneo

Testing en pipelines de CI/CD

Integrá la API de SMSCode en tu pipeline de CI/CD para ejecutar pruebas de flujo de verificación reales en el entorno de staging antes de cada deploy. Esto detecta regresiones en el flujo de onboarding antes de que lleguen a producción.

El token de API para testing puede ser diferente al de producción — creá tokens separados en el dashboard para distintos entornos y rotarlos según tu política de seguridad.

Verificación multi-país para testing de cobertura regional

Si tu app soporta usuarios de México, Colombia, Argentina y Chile, podés probar el flujo de verificación con números locales de cada país usando el mismo token de API — simplemente cambiás el parámetro country al consultar el catálogo.

countries = ["mexico", "colombia", "argentina", "chile"]
for country in countries:
    params = {"country": country, "platform": "tu_plataforma"}
    products = requests.get(
        f"{BASE_URL}/catalog/products",
        headers=headers,
        params=params
    ).json()["data"]
    # crear pedido, hacer polling, verificar que el OTP llega

Revisá cómo elegir el país adecuado para más contexto sobre qué países tienen mejor tasa de éxito para cada plataforma.

Próximas funcionalidades

Los webhooks están en el roadmap activo. En lugar de hacer polling activo, tu endpoint recibirá una notificación push en el momento exacto en que llegue el OTP. Esto simplificará enormemente las integraciones orientadas a eventos y eliminará la necesidad del loop de polling. Suscribite a las actualizaciones desde el dashboard para recibir notificaciones cuando esté disponible.

Para una visión más amplia de SMSCode y sus casos de uso, consultá la evaluación completa de SMSCode. Si el costo es un factor clave para tu integración de alto volumen, la guía de mejores servicios de números virtuales 2026 compara las alternativas del mercado.


FAQ

¿Dónde encuentro mi token de API?

En el dashboard de SMSCode bajo Configuración de cuenta. Si aún no tenés cuenta, registrate aquí — toma menos de un minuto y no necesitás número de teléfono.

¿Existe un modo sandbox o de pruebas?

Actualmente no hay entorno sandbox separado. Todas las llamadas a la API operan contra el sistema real con números reales. Para desarrollo y testing inicial, usá los productos más económicos disponibles (Indonesia, India). Los costos de testing se mantienen muy bajos cuando solo validás la lógica de tu integración.

¿Cuál es la diferencia entre COMPLETED y OTP_RECEIVED?

OTP_RECEIVED significa que el SMS llegó y el código está disponible en el campo otp_code de la respuesta. COMPLETED significa que el ciclo de vida del pedido terminó completamente. En la práctica de tu integración, actuás sobre OTP_RECEIVED — ahí es cuando el código está disponible para usar.

¿Puedo recibir múltiples OTPs en el mismo número?

Sí, durante el período activo del pedido. Si la plataforma destino envía un segundo SMS al mismo número (por ejemplo, un código de confirmación después del código inicial, o un intento de reenvío), el campo otp_code se actualiza al último código recibido. Continuá haciendo polling hasta tener lo que necesitás o hasta que el pedido expire.

¿Cómo manejo una verificación fallida donde sí llegó el OTP?

Si el OTP llegó correctamente pero el servicio destino lo rechazó (código incorrecto por tipeo, expirado en el servicio destino, validación fallida por otra razón), no hay reembolso porque la entrega de SMS fue exitosa. Creá un nuevo pedido con un número diferente y reintentá el flujo completo. Para este caso, el costo extra es el precio de un número adicional.

¿La API tiene límites de volumen para cuentas estándar?

Los rate limits están diseñados para cubrir cargas de automatización normales sin restricciones en el uso cotidiano. Si tenés un caso de uso de muy alto volumen — miles de verificaciones por hora — contactá al soporte antes de escalar para coordinar la capacidad necesaria.

¿Listo para probar SMSCode?

Crea una cuenta y obtén tu primer número virtual en menos de dos minutos.

Comenzar →