Acceso programático a números virtuales, pedidos y saldo de cuenta.
Recomendado
⟩Empieza con los SDK oficiales
Usa el SDK de TypeScript/JavaScript o Python para nuevas integraciones. Ambos SDK usan por defecto la API pública /v2, conservan claves de idempotencia en reintentos seguros, exponen errores tipados y mantienen consistente el ciclo de vida del OTP.
Crea un pedido con product_id para una ranura de nivel exacta y estable, o con catalog_product_id, operator_id opcional, min_price/max_price y una clave de idempotencia para llamadas pagadas enrutadas seguras al reintentar.
catalog_product_idmax_priceIdempotency-Key
02
Usa el OTP
Espera el OTP, envíalo en tu app destino y llama a finish para cerrar la orden.
waitForOtpwait_for_otpfinish
03
Reenvía solo si hace falta
Después de reenviar, espera un nuevo OTP con afterCode en TypeScript o after_code en Python.
can_resendresend_available_at
import { SmscodeClient, OtpTimeoutError } from "@smscode/sdk";const client = new SmscodeClient({ token: process.env.SMSCODE_TOKEN! });let orderId: number | undefined;try { const created = await client.orders.create({ catalog_product_id: Number(process.env.SMSCODE_CATALOG_PRODUCT_ID), max_price: "0.50", quantity: 1, }); const order = created.orders[0]!; orderId = order.id; const first = await client.orders.waitForOtp(orderId, { timeoutMs: 120_000 }); console.log(first.otpCode); // Envía este OTP en la app destino. await client.orders.finish(orderId);} catch (err) { if (err instanceof OtpTimeoutError && orderId !== undefined) { const current = await client.orders.get(orderId); if (current.can_cancel) await client.orders.cancel(orderId); } throw err;}
import osfrom smscode import OtpTimeoutError, SmscodeClientwith SmscodeClient(token=os.environ["SMSCODE_TOKEN"]) as client: created = client.orders.create( catalog_product_id=int(os.environ["SMSCODE_CATALOG_PRODUCT_ID"]), max_price="0.50", quantity=1, ) order = created.orders[0] order_id = int(order["id"]) try: first = client.orders.wait_for_otp(order_id, timeout_ms=120_000) print(first.otp_code) # Envía este OTP en la app destino. client.orders.finish(order_id) except OtpTimeoutError: current = client.orders.get(order_id) if current["can_cancel"]: client.orders.cancel(order_id) raise
Usa can_resend y resend_available_at para el tiempo de reenvío. Los timestamps de reenvío de bajo nivel son internos y no son campos públicos de respuesta.
⟩Vision General
Todos los campos monetarios de la API /v1 están en IDR (rupia indonesia), como unidades enteras — por ejemplo, "price": 15000 y "balance": 500000 significan Rp 15.000 y Rp 500.000. Para una proyección nativa en USD del mismo libro mayor, cambia a la API v2 con el selector de versión de arriba.
⟩Autenticación
Todas las solicitudes a la API requieren un Bearer token. Genera uno desde Configuración de la Cuenta en el panel, y luego inclúyelo en cada solicitud:
Authorization:Bearer YOUR_API_TOKEN
Las solicitudes sin un token válido reciben una respuesta 401 UNAUTHORIZED.
⟩URL Base
Todas las rutas de endpoints a continuación son relativas a:
https://api.smscode.gg/v1
⟩Formato de Respuesta
Cada respuesta devuelve JSON con una estructura consistente. Todas las respuestas incluyen un encabezado x-request-id para depuración.
Todos los campos monetarios de la API /v1 están en IDR (rupia indonesia), como unidades enteras — por ejemplo, "price": 15000 y "balance": 500000 significan Rp 15.000 y Rp 500.000. Para una proyección nativa en USD del mismo libro mayor, cambia a la API v2 con el selector de versión de arriba.
GET/catalog/countries
Devuelve una lista de todos los países disponibles.
Devuelve los operadores seleccionables para un país + servicio. Si hay operadores reales y stock Any disponibles, la respuesta incluye una fila Any con operator_id null; si no hay productos específicos de operador, la lista está vacía.
Crea un nuevo pedido de número virtual. Deduce el saldo automáticamente. Admite un encabezado opcional Idempotency-Key para evitar pedidos duplicados en reintentos de red.
Cuerpo de la Solicitud
Nombre
Tipo
Requerido
Descripción
product_id
integer
No
ID de producto de ranura de nivel exacta y estable para pedir directamente. Envía ESTA o catalog_product_id, no ambas.
catalog_product_id
integer
No
ID umbrella enrutado de país+plataforma. El servidor elige un nivel actual que coincida. Envía este o product_id.
operator_id
integer
No
ID de operador opcional de /catalog/operators. Solo válido con catalog_product_id; omítelo para Any.
min_price
integer
No
Precio mínimo opcional. Entero en IDR. Solo válido con catalog_product_id.
max_price
integer
No
Límite de precio opcional. Entero en IDR. Solo válido con catalog_product_id.
prefer_provider
string
No
Código de proveedor opcional a preferir cuando las ofertas empatan.
policy
string
No
Política de enrutamiento opcional, válida solo con catalog_product_id. Valores: cheapest (predeterminado) elige la oferta sana más barata; best_success ordena las ofertas primero por el éxito de entrega reciente. best_success puntúa a cada proveedor según la proporción de pedidos que recibieron OTP en los últimos 30 días completos, en franjas del 10%, y solo cuenta a un proveedor cuando tiene al menos 20 pedidos en ese periodo — los proveedores por debajo de ese umbral o sin historial se tratan como neutrales, de modo que las ofertas nuevas nunca quedan relegadas (opcional; la señal arranca neutral). Si también se indica prefer_provider, el proveedor preferido sigue quedando primero.
quantity
integer
No
Cantidad de artículos (1-100, predeterminado 1)
Pasa un encabezado Idempotency-Key para reintentar de forma segura sin crear pedidos duplicados. La clave puede contener letras, dígitos, guion y guion bajo (A-Z a-z 0-9 _ -), hasta 128 caracteres; una clave no válida se rechaza con 422 VALIDATION_ERROR. Reintentar con la misma clave y el mismo cuerpo reproduce el resultado original (incluido el failed_count de un éxito parcial). Un reintento que llega al proveedor pero falla queda registrado y reproduce ese mismo error — usa una clave NUEVA para volver a intentarlo. Los fallos sin efectos secundarios (saldo insuficiente, sin oferta disponible) liberan la clave, así que puedes recargar saldo y reintentar con la misma clave. Reutilizar una clave con un cuerpo distinto devuelve 422 IDEMPOTENCY_KEY_REUSED, y una solicitud aún en curso con esa clave devuelve 409 REQUEST_IN_PROGRESS. El campo failed_reason en las respuestas de create siempre es null — solo se rellena al consultar/listar pedidos.
Ejemplo de Solicitud
curl -s -X POST https://api.smscode.gg/v1/orders/create \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: unique-request-id-123" \ -d '{"product_id":142,"quantity":1}'# Or route by catalog_product_id — the server picks a current tier.# product_id is the stable exact tier-slot id. catalog_product_id is the# country+platform umbrella for routed ordering. Optional min_price/max_price# bound the tier; operator_id scopes to a carrier from /catalog/operators.# Pass EITHER product_id OR catalog_product_id.curl -s -X POST https://api.smscode.gg/v1/orders/create \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: unique-request-id-124" \ -d '{"catalog_product_id":87,"min_price":12000,"max_price":20000,"operator_id":433}'
Solicita que la plataforma reenvíe el SMS al número alquilado. No todas las plataformas soportan el reenvío — verifica el campo resent en la respuesta.
Reactiva un número completado — vuelve a pedir el mismo número para otro código de verificación, sin alquilar uno nuevo. Solo califica un pedido completado cuyo número admita la reactivación (comprueba can_reactivate en el pedido o previsualiza con reactivate-options). El hijo reactivado es un pedido NUEVO, devuelto con la misma forma que create; el saldo se cobra automáticamente.
Cuerpo de la Solicitud
Nombre
Tipo
Requerido
Descripción
id
integer
Sí
El pedido completado que se va a reactivar.
max_price
integer
No
Límite de costo opcional. Entero en IDR. La reactivación se rechaza con 422 VALIDATION_ERROR si el costo actual lo supera.
Al igual que create, esta es una mutación monetaria — pasa un encabezado Idempotency-Key para reintentar de forma segura (un create y un reactivate nunca pueden colisionar en una misma clave). Reutilizar una clave con un cuerpo distinto devuelve 422 IDEMPOTENCY_KEY_REUSED, y una solicitud aún en curso con esa clave devuelve 409 REQUEST_IN_PROGRESS. Un número que no se puede reactivar devuelve 409 CONFLICT; un saldo demasiado bajo devuelve 409 INSUFFICIENT_BALANCE.
Previsualiza cuánto cobraría una reactivación en este momento. Solo lectura — no consume ningún Idempotency-Key ni crea nada. Devuelve el costo como un entero en IDR. Disponible solo para un pedido completado cuyo número admita la reactivación.
Parámetros de Ruta
Nombre
Tipo
Requerido
Descripción
id
integer
Sí
ID del pedido para el que previsualizar el costo de reactivación (parámetro de ruta).
Actualiza tu URL de webhook y/o secreto. Se genera un secreto automáticamente cuando estableces una URL por primera vez. Envía una cadena vacía para borrar. La URL debe usar HTTPS.
Cuerpo de la Solicitud
Nombre
Tipo
Requerido
Descripción
webhook_url
string
No
URL HTTPS para recibir eventos webhook (cadena vacía para borrar)
webhook_secret
string
No
Secreto compartido para firma HMAC-SHA256 (se genera automáticamente si se omite en la primera configuración)
Envía un evento de prueba a tu URL de webhook configurada. Devuelve el código de estado HTTP de tu servidor. Útil para verificar que tu endpoint funciona antes de ponerlo en producción.
Parámetros
Ninguno
Ejemplo de Solicitud
curl -s -X POST https://api.smscode.gg/v1/webhook/test \ -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v1/webhook/test", { method: "POST", headers: { Authorization: "Bearer YOUR_API_TOKEN" },});const data = await res.json();
Configura una URL de webhook para recibir notificaciones push en tiempo real de eventos de pedidos en lugar de hacer polling. Este es el enfoque recomendado para scripts de bots.
Eventos
Evento
Disparador
order.otp_received
Nuevo SMS entregado; el código detectado puede ser null
order.completed
Pedido marcado como completado (manual o por expiración)
order.expired
Pedido expirado antes de recibir un SMS (saldo reembolsado)
order.canceled
Pedido cancelado por el usuario (saldo reembolsado)
Cada SMS nuevo emite este evento. otp_code puede ser null mientras otp_message está presente. Varios eventos SMS pueden llegar fuera de orden; usa sms_revision para ignorar un par agregado más antiguo.
Cada solicitud webhook incluye un encabezado X-Webhook-Signature con una firma HMAC-SHA256 del cuerpo de la solicitud, usando tu webhook_secret como clave:
Verifica esta firma en tu servidor para asegurar que la solicitud es auténtica. La entrega es de tipo disparar y olvidar con un tiempo de espera de 3 segundos y sin reintentos.
⟩Límites de solicitudes
Las solicitudes a la API tienen rate limits por grupo de endpoints. Exceder el límite devuelve 429 Too Many Requests con un encabezado Retry-After indicando cuántos segundos esperar.
Grupo de Endpoints
Límite
Ventana
Catálogo (países, servicios, productos, tasa de cambio)
5.000 solicitudes
60 segundos
Saldo
600 solicitudes
60 segundos
Lectura de pedidos (listar, obtener, activos)
5.000 solicitudes
60 segundos
Crear pedido
3.000 solicitudes
60 segundos
Cancelar pedido
1.000 solicitudes
60 segundos
Acciones de pedido (finalizar, reenviar)
1.000 solicitudes
60 segundos
Configuración de webhook (obtener, actualizar)
600 solicitudes
60 segundos
Prueba de webhook
10 solicitudes
60 segundos
⟩Códigos de Error
Las respuestas de error incluyen uno de estos códigos en error.code:
Código
HTTP
Descripción
UNAUTHORIZED
401
Token de la API faltante o inválido
FORBIDDEN
403
Acceso denegado
NOT_FOUND
404
Recurso no encontrado (pedido, tasa de cambio, etc.)
CONFLICT
409
Solicitud duplicada o conflicto de recursos
INSUFFICIENT_BALANCE
409
Saldo insuficiente para crear el pedido
VALIDATION_ERROR
422
Los parámetros de la solicitud no pasaron la validación
RATE_LIMIT_EXCEEDED
429
Demasiadas solicitudes (verifica el encabezado Retry-After)
INTERNAL_ERROR
500
Error interno del servidor
PROVIDER_ERROR
422
El proveedor SMS ascendente rechazó la solicitud. En fallos de creación de pedido, el error puede incluir details: cause_counts (pedidos con product_id heredado — un recuento agrupado por causa) o attempts (pedidos con catalog_product_id — resultados por intento), usando los valores ok, no_numbers, insufficient_balance, price_rejected, provider_unavailable, provider_account_balance, provider_error.
NO_OFFER_AVAILABLE
422
Ninguna oferta activa coincide con el producto y la política solicitados (límite de precio, disponibilidad).
CANCEL_TOO_EARLY
409
Pedido demasiado reciente para cancelar — espera 2 minutos
REQUEST_IN_PROGRESS
409
Una solicitud de creación con esta clave de idempotencia aún está en curso
IDEMPOTENCY_KEY_REUSED
422
Esta clave de idempotencia ya se usó con un cuerpo de solicitud diferente
SERVICE_UNAVAILABLE
503
Servicio temporalmente no disponible (mantenimiento)
⟩Vision General
Todos los campos monetarios de la API /v2 están en USD y se devuelven como un objeto monetario — { "amount": "0.92", "currency": "USD", "canonical_amount": 15000, "canonical_currency": "IDR" }. amount es una cadena decimal; canonical_amount es el valor exacto en IDR del libro mayor (úsalo para la conciliación). La rate USD/IDR aplicada se revela una vez por respuesta en meta.fx. v2 es una proyección en USD en tiempo de renderizado sobre el mismo libro mayor en IDR que v1 — nunca almacena ni transacciona en USD.
Todas las solicitudes a la API requieren un Bearer token. Genera uno desde Configuración de la Cuenta en el panel, y luego inclúyelo en cada solicitud:
Authorization:Bearer YOUR_API_TOKEN
Las solicitudes sin un token válido reciben una respuesta 401 UNAUTHORIZED.
⟩URL Base
Todas las rutas de endpoints a continuación son relativas a:
https://api.smscode.gg/v2
⟩Formato de Respuesta
Cada respuesta devuelve JSON con una estructura consistente. Todas las respuestas incluyen un encabezado x-request-id para depuración.
Todos los campos monetarios de la API /v2 están en USD y se devuelven como un objeto monetario — { "amount": "0.92", "currency": "USD", "canonical_amount": 15000, "canonical_currency": "IDR" }. amount es una cadena decimal; canonical_amount es el valor exacto en IDR del libro mayor (úsalo para la conciliación). La rate USD/IDR aplicada se revela una vez por respuesta en meta.fx. v2 es una proyección en USD en tiempo de renderizado sobre el mismo libro mayor en IDR que v1 — nunca almacena ni transacciona en USD.
GET/catalog/countries
Devuelve una lista de todos los países disponibles.
Idéntico a v1 — solo cambia la ruta base (/v1 → /v2).
GET/catalog/operators
Devuelve los operadores seleccionables para un país + servicio. Si hay operadores reales y stock Any disponibles, la respuesta incluye una fila Any con operator_id null; si no hay productos específicos de operador, la lista está vacía.
v2: los campos monetarios son objetos monetarios en USD y la respuesta lleva un único meta.fx { pair, rate, rate_as_of }. rate es el valor entero en IDR por 1 USD, así que USD = canonical_amount / rate. Los totales usan 2 decimales; los precios/reembolsos por ítem usan 4. Un importe estrictamente positivo nunca se redondea a 0.00. rate_as_of es la marca de tiempo RFC3339 de la tasa (en formato +00:00) o null cuando no se registra ninguna marca de tiempo.
Solo v2: si no existe una tasa USD/IDR utilizable, los endpoints monetarios devuelven 503 FX_RATE_UNAVAILABLE con una cabecera Retry-After en lugar de un cuerpo monetario. v1 nunca devuelve esto.
GET/catalog/exchange-rate
Devuelve la tasa de cambio USD/IDR actual usada para la conversión de divisas.
Parámetros
Ninguno — v2 siempre devuelve USD/IDR; el parámetro ?pair de v1 se ignora.
v2: devuelve { pair, rate, rate_as_of } (sin base_currency/quote_currency, sin el envoltorio meta — la tasa es el dato). ?pair se ignora — v2 siempre devuelve USD/IDR (v1 respeta ?pair). Devuelve 503 FX_RATE_UNAVAILABLE si no existe una tasa utilizable.
GET/balance
Devuelve el saldo de cuenta del usuario autenticado.
v2: los campos monetarios son objetos monetarios en USD y la respuesta lleva un único meta.fx { pair, rate, rate_as_of }. rate es el valor entero en IDR por 1 USD, así que USD = canonical_amount / rate. Los totales usan 2 decimales; los precios/reembolsos por ítem usan 4. Un importe estrictamente positivo nunca se redondea a 0.00. rate_as_of es la marca de tiempo RFC3339 de la tasa (en formato +00:00) o null cuando no se registra ninguna marca de tiempo.
Solo v2: si no existe una tasa USD/IDR utilizable, los endpoints monetarios devuelven 503 FX_RATE_UNAVAILABLE con una cabecera Retry-After en lugar de un cuerpo monetario. v1 nunca devuelve esto.
GET/orders
Devuelve una lista de los pedidos del usuario autenticado, ordenados por más recientes. Admite filtrado por estado y paginación mediante offset.
Parámetros de Consulta
Nombre
Tipo
Requerido
Descripción
limit
integer
No
Máximo de resultados (1-100, predeterminado 20)
offset
integer
No
Número de resultados a omitir (predeterminado 0)
status
string
No
Filtrar por estado: ACTIVE, OTP_RECEIVED, COMPLETED, CANCELED, EXPIRED (sin distinción de mayúsculas)
v2: los campos monetarios son objetos monetarios en USD y la respuesta lleva un único meta.fx { pair, rate, rate_as_of }. rate es el valor entero en IDR por 1 USD, así que USD = canonical_amount / rate. Los totales usan 2 decimales; los precios/reembolsos por ítem usan 4. Un importe estrictamente positivo nunca se redondea a 0.00. rate_as_of es la marca de tiempo RFC3339 de la tasa (en formato +00:00) o null cuando no se registra ninguna marca de tiempo.
Solo v2: si no existe una tasa USD/IDR utilizable, los endpoints monetarios devuelven 503 FX_RATE_UNAVAILABLE con una cabecera Retry-After en lugar de un cuerpo monetario. v1 nunca devuelve esto.
GET/orders/{id}
Devuelve un pedido individual por ID. Solo devuelve pedidos del usuario autenticado.
v2: los campos monetarios son objetos monetarios en USD y la respuesta lleva un único meta.fx { pair, rate, rate_as_of }. rate es el valor entero en IDR por 1 USD, así que USD = canonical_amount / rate. Los totales usan 2 decimales; los precios/reembolsos por ítem usan 4. Un importe estrictamente positivo nunca se redondea a 0.00. rate_as_of es la marca de tiempo RFC3339 de la tasa (en formato +00:00) o null cuando no se registra ninguna marca de tiempo.
Solo v2: si no existe una tasa USD/IDR utilizable, los endpoints monetarios devuelven 503 FX_RATE_UNAVAILABLE con una cabecera Retry-After en lugar de un cuerpo monetario. v1 nunca devuelve esto.
GET/orders/active
Lista todos los pedidos actualmente activos (ACTIVE + OTP_RECEIVED). Úsalo para consultar actualizaciones de estado de OTP.
v2: este endpoint no es monetario — no devuelve amount ni meta.fx (la misma estructura que v1, bajo /v2).
POST/orders/create
Crea un nuevo pedido de número virtual. Deduce el saldo automáticamente. Admite un encabezado opcional Idempotency-Key para evitar pedidos duplicados en reintentos de red.
Cuerpo de la Solicitud
Nombre
Tipo
Requerido
Descripción
product_id
integer
No
ID de producto de ranura de nivel exacta y estable para pedir directamente. Envía ESTA o catalog_product_id, no ambas.
catalog_product_id
integer
No
ID umbrella enrutado de país+plataforma. El servidor elige un nivel actual que coincida. Envía este o product_id.
operator_id
integer
No
ID de operador opcional de /catalog/operators. Solo válido con catalog_product_id; omítelo para Any.
min_price
string
No
Precio mínimo opcional. Cadena decimal en USD (p. ej. "0.30"). Solo válido con catalog_product_id.
max_price
string
No
Límite de precio opcional. Cadena decimal en USD (p. ej. "0.50"). Solo válido con catalog_product_id.
prefer_provider
string
No
Código de proveedor opcional a preferir cuando las ofertas empatan.
policy
string
No
Política de enrutamiento opcional, válida solo con catalog_product_id. Valores: cheapest (predeterminado) elige la oferta sana más barata; best_success ordena las ofertas primero por el éxito de entrega reciente. best_success puntúa a cada proveedor según la proporción de pedidos que recibieron OTP en los últimos 30 días completos, en franjas del 10%, y solo cuenta a un proveedor cuando tiene al menos 20 pedidos en ese periodo — los proveedores por debajo de ese umbral o sin historial se tratan como neutrales, de modo que las ofertas nuevas nunca quedan relegadas (opcional; la señal arranca neutral). Si también se indica prefer_provider, el proveedor preferido sigue quedando primero.
quantity
integer
No
Cantidad de artículos (1-100, predeterminado 1)
Pasa un encabezado Idempotency-Key para reintentar de forma segura sin crear pedidos duplicados. La clave puede contener letras, dígitos, guion y guion bajo (A-Z a-z 0-9 _ -), hasta 128 caracteres; una clave no válida se rechaza con 422 VALIDATION_ERROR. Reintentar con la misma clave y el mismo cuerpo reproduce el resultado original (incluido el failed_count de un éxito parcial). Un reintento que llega al proveedor pero falla queda registrado y reproduce ese mismo error — usa una clave NUEVA para volver a intentarlo. Los fallos sin efectos secundarios (saldo insuficiente, sin oferta disponible) liberan la clave, así que puedes recargar saldo y reintentar con la misma clave. Reutilizar una clave con un cuerpo distinto devuelve 422 IDEMPOTENCY_KEY_REUSED, y una solicitud aún en curso con esa clave devuelve 409 REQUEST_IN_PROGRESS. El campo failed_reason en las respuestas de create siempre es null — solo se rellena al consultar/listar pedidos.
v2: los campos monetarios son objetos monetarios en USD y la respuesta lleva un único meta.fx { pair, rate, rate_as_of }. rate es el valor entero en IDR por 1 USD, así que USD = canonical_amount / rate. Los totales usan 2 decimales; los precios/reembolsos por ítem usan 4. Un importe estrictamente positivo nunca se redondea a 0.00. rate_as_of es la marca de tiempo RFC3339 de la tasa (en formato +00:00) o null cuando no se registra ninguna marca de tiempo.
Solo v2: si no existe una tasa USD/IDR utilizable, los endpoints monetarios devuelven 503 FX_RATE_UNAVAILABLE con una cabecera Retry-After en lugar de un cuerpo monetario. v1 nunca devuelve esto.
POST/orders/cancel
Cancela un pedido activo. El costo del alquiler se reembolsa al saldo de tu cuenta.
v2: los campos monetarios son objetos monetarios en USD y la respuesta lleva un único meta.fx { pair, rate, rate_as_of }. rate es el valor entero en IDR por 1 USD, así que USD = canonical_amount / rate. Los totales usan 2 decimales; los precios/reembolsos por ítem usan 4. Un importe estrictamente positivo nunca se redondea a 0.00. rate_as_of es la marca de tiempo RFC3339 de la tasa (en formato +00:00) o null cuando no se registra ninguna marca de tiempo.
Solo v2: si no existe una tasa USD/IDR utilizable, los endpoints monetarios devuelven 503 FX_RATE_UNAVAILABLE con una cabecera Retry-After en lugar de un cuerpo monetario. v1 nunca devuelve esto.
POST/orders/finish
Marca un pedido como completado después de recibir el OTP. Esto libera el número inmediatamente en lugar de esperar a que expire.
Idéntico a v1 — solo cambia la ruta base (/v1 → /v2).
POST/orders/resend
Solicita que la plataforma reenvíe el SMS al número alquilado. No todas las plataformas soportan el reenvío — verifica el campo resent en la respuesta.
Idéntico a v1 — solo cambia la ruta base (/v1 → /v2).
POST/orders/reactivate
Reactiva un número completado — vuelve a pedir el mismo número para otro código de verificación, sin alquilar uno nuevo. Solo califica un pedido completado cuyo número admita la reactivación (comprueba can_reactivate en el pedido o previsualiza con reactivate-options). El hijo reactivado es un pedido NUEVO, devuelto con la misma forma que create; el saldo se cobra automáticamente.
Cuerpo de la Solicitud
Nombre
Tipo
Requerido
Descripción
id
integer
Sí
El pedido completado que se va a reactivar.
max_price
string
No
Límite de costo opcional. Cadena decimal en USD (p. ej. "0.50"). La reactivación se rechaza con 422 VALIDATION_ERROR si el costo actual lo supera.
Al igual que create, esta es una mutación monetaria — pasa un encabezado Idempotency-Key para reintentar de forma segura (un create y un reactivate nunca pueden colisionar en una misma clave). Reutilizar una clave con un cuerpo distinto devuelve 422 IDEMPOTENCY_KEY_REUSED, y una solicitud aún en curso con esa clave devuelve 409 REQUEST_IN_PROGRESS. Un número que no se puede reactivar devuelve 409 CONFLICT; un saldo demasiado bajo devuelve 409 INSUFFICIENT_BALANCE.
v2: los campos monetarios son objetos monetarios en USD y la respuesta lleva un único meta.fx { pair, rate, rate_as_of }. rate es el valor entero en IDR por 1 USD, así que USD = canonical_amount / rate. Los totales usan 2 decimales; los precios/reembolsos por ítem usan 4. Un importe estrictamente positivo nunca se redondea a 0.00. rate_as_of es la marca de tiempo RFC3339 de la tasa (en formato +00:00) o null cuando no se registra ninguna marca de tiempo.
Solo v2: si no existe una tasa USD/IDR utilizable, los endpoints monetarios devuelven 503 FX_RATE_UNAVAILABLE con una cabecera Retry-After en lugar de un cuerpo monetario. v1 nunca devuelve esto.
GET/orders/{id}/reactivate-options
Previsualiza cuánto cobraría una reactivación en este momento. Solo lectura — no consume ningún Idempotency-Key ni crea nada. Devuelve el costo como un objeto monetario en USD con un recibo de FX. Disponible solo para un pedido completado cuyo número admita la reactivación.
Parámetros de Ruta
Nombre
Tipo
Requerido
Descripción
id
integer
Sí
ID del pedido para el que previsualizar el costo de reactivación (parámetro de ruta).
v2: los campos monetarios son objetos monetarios en USD y la respuesta lleva un único meta.fx { pair, rate, rate_as_of }. rate es el valor entero en IDR por 1 USD, así que USD = canonical_amount / rate. Los totales usan 2 decimales; los precios/reembolsos por ítem usan 4. Un importe estrictamente positivo nunca se redondea a 0.00. rate_as_of es la marca de tiempo RFC3339 de la tasa (en formato +00:00) o null cuando no se registra ninguna marca de tiempo.
Solo v2: si no existe una tasa USD/IDR utilizable, los endpoints monetarios devuelven 503 FX_RATE_UNAVAILABLE con una cabecera Retry-After en lugar de un cuerpo monetario. v1 nunca devuelve esto.
GET/webhook
Devuelve tu configuración actual de notificaciones webhook.
Idéntico a v1 — solo cambia la ruta base (/v1 → /v2).
PATCH/webhook
Actualiza tu URL de webhook y/o secreto. Se genera un secreto automáticamente cuando estableces una URL por primera vez. Envía una cadena vacía para borrar. La URL debe usar HTTPS.
Cuerpo de la Solicitud
Nombre
Tipo
Requerido
Descripción
webhook_url
string
No
URL HTTPS para recibir eventos webhook (cadena vacía para borrar)
webhook_secret
string
No
Secreto compartido para firma HMAC-SHA256 (se genera automáticamente si se omite en la primera configuración)
Idéntico a v1 — solo cambia la ruta base (/v1 → /v2).
POST/webhook/test
Envía un evento de prueba a tu URL de webhook configurada. Devuelve el código de estado HTTP de tu servidor. Útil para verificar que tu endpoint funciona antes de ponerlo en producción.
Parámetros
Ninguno
Ejemplo de Solicitud
curl -s -X POST https://api.smscode.gg/v2/webhook/test \ -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v2/webhook/test", { method: "POST", headers: { Authorization: "Bearer YOUR_API_TOKEN" },});const data = await res.json();
Idéntico a v1 — solo cambia la ruta base (/v1 → /v2).
⟩Notificaciones Webhook
Configura una URL de webhook para recibir notificaciones push en tiempo real de eventos de pedidos en lugar de hacer polling. Este es el enfoque recomendado para scripts de bots.
Eventos
Evento
Disparador
order.otp_received
Nuevo SMS entregado; el código detectado puede ser null
order.completed
Pedido marcado como completado (manual o por expiración)
order.expired
Pedido expirado antes de recibir un SMS (saldo reembolsado)
order.canceled
Pedido cancelado por el usuario (saldo reembolsado)
Cada SMS nuevo emite este evento. otp_code puede ser null mientras otp_message está presente. Varios eventos SMS pueden llegar fuera de orden; usa sms_revision para ignorar un par agregado más antiguo.
Cada solicitud webhook incluye un encabezado X-Webhook-Signature con una firma HMAC-SHA256 del cuerpo de la solicitud, usando tu webhook_secret como clave:
Verifica esta firma en tu servidor para asegurar que la solicitud es auténtica. La entrega es de tipo disparar y olvidar con un tiempo de espera de 3 segundos y sin reintentos.
⟩Límites de solicitudes
Las solicitudes a la API tienen rate limits por grupo de endpoints. Exceder el límite devuelve 429 Too Many Requests con un encabezado Retry-After indicando cuántos segundos esperar.
Grupo de Endpoints
Límite
Ventana
Catálogo (países, servicios, productos, tasa de cambio)
5.000 solicitudes
60 segundos
Saldo
600 solicitudes
60 segundos
Lectura de pedidos (listar, obtener, activos)
5.000 solicitudes
60 segundos
Crear pedido
3.000 solicitudes
60 segundos
Cancelar pedido
1.000 solicitudes
60 segundos
Acciones de pedido (finalizar, reenviar)
1.000 solicitudes
60 segundos
Configuración de webhook (obtener, actualizar)
600 solicitudes
60 segundos
Prueba de webhook
10 solicitudes
60 segundos
⟩Códigos de Error
Las respuestas de error incluyen uno de estos códigos en error.code:
Código
HTTP
Descripción
UNAUTHORIZED
401
Token de la API faltante o inválido
FORBIDDEN
403
Acceso denegado
NOT_FOUND
404
Recurso no encontrado (pedido, tasa de cambio, etc.)
CONFLICT
409
Solicitud duplicada o conflicto de recursos
INSUFFICIENT_BALANCE
409
Saldo insuficiente para crear el pedido
VALIDATION_ERROR
422
Los parámetros de la solicitud no pasaron la validación
RATE_LIMIT_EXCEEDED
429
Demasiadas solicitudes (verifica el encabezado Retry-After)
INTERNAL_ERROR
500
Error interno del servidor
PROVIDER_ERROR
422
El proveedor SMS ascendente rechazó la solicitud. En fallos de creación de pedido, el error puede incluir details: cause_counts (pedidos con product_id heredado — un recuento agrupado por causa) o attempts (pedidos con catalog_product_id — resultados por intento), usando los valores ok, no_numbers, insufficient_balance, price_rejected, provider_unavailable, provider_account_balance, provider_error.
NO_OFFER_AVAILABLE
422
Ninguna oferta activa coincide con el producto y la política solicitados (límite de precio, disponibilidad).
CANCEL_TOO_EARLY
409
Pedido demasiado reciente para cancelar — espera 2 minutos
REQUEST_IN_PROGRESS
409
Una solicitud de creación con esta clave de idempotencia aún está en curso
IDEMPOTENCY_KEY_REUSED
422
Esta clave de idempotencia ya se usó con un cuerpo de solicitud diferente
SERVICE_UNAVAILABLE
503
Servicio temporalmente no disponible (mantenimiento)
FX_RATE_UNAVAILABLE
503
Tasa de cambio USD/IDR no disponible (endpoints monetarios de v2) — devuelve 503 con una cabecera Retry-After.
v1 → v2
⟩Migración de v1 a v2
v1 sirve IDR; v2 sirve USD. Ambas versiones coexisten de forma permanente — no hay descontinuación. Elige una versión por integración; no mezcles rutas base. v2 es idéntica a v1 salvo en cómo se representa el dinero.
product_id es el ID estable de la ranura de nivel de SMSCode. Guárdalo si quieres pedir exactamente ese nivel; su precio y disponibilidad pueden cambiar en la misma fila. catalog_product_id es el umbrella estable de país+plataforma para pedidos enrutados; úsalo con operator_id, min_price, max_price, prefer_provider y policy opcionales cuando quieras que el servidor elija un nivel actual que coincida.
Parsea los campos monetarios como objetos — lee amount como una cadena decimal; currency es "USD".
Para la conciliación del libro mayor usa canonical_amount (IDR exacto); el amount en USD es una proyección en tiempo de renderizado y la rate se revela una vez en meta.fx.
Maneja el nuevo FX_RATE_UNAVAILABLE (503) — reintenta después de Retry-After. v1 nunca devuelve esto.