Documentación de la API

Acceso programático a números virtuales, pedidos y saldo de cuenta.

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.

Éxito
{
  "success": true,
  "data": { ... }
}
Error
{
  "success": false,
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable message"
  }
}

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.

Parámetros

Ninguno

Ejemplo de Solicitud

curl -s https://api.smscode.gg/v1/catalog/countries \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v1/catalog/countries", {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v1/catalog/countries",
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

Ejemplo de Respuesta

200 OK
{
  "success": true,
  "data": [
    {
      "id": 6,
      "code": "ID",
      "name": "Indonesia",
      "dial_code": "+62",
      "emoji": "🇮🇩",
      "active": true
    }
  ]
}
GET /catalog/services

Devuelve una lista de servicios (plataformas) disponibles. Opcionalmente filtra por país.

Parámetros de Consulta

NombreTipoRequeridoDescripción
country_idintegerNoFiltrar servicios disponibles para este país

Ejemplo de Solicitud

curl -s "https://api.smscode.gg/v1/catalog/services?country_id=6" \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v1/catalog/services?country_id=6", {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v1/catalog/services",
    params={"country_id": 6},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

Ejemplo de Respuesta

200 OK
{
  "success": true,
  "data": [
    {
      "id": 3,
      "code": "wa",
      "name": "WhatsApp",
      "active": true
    }
  ]
}
GET /catalog/products

Devuelve una lista paginada de productos disponibles. Filtra por país y/o plataforma.

Parámetros de Consulta

NombreTipoRequeridoDescripción
country_idintegerNoFiltrar por ID de país
platform_idintegerNoFiltrar por ID de plataforma/servicio
sortstringNoOrden de clasificación: price_asc (predeterminado), price_desc, available_asc, available_desc, name_asc, name_desc
limitintegerNoResultados por página (1-10.000, predeterminado 1.000)
pageintegerNoNúmero de página (mínimo 1, predeterminado 1)

Ejemplo de Solicitud

curl -s "https://api.smscode.gg/v1/catalog/products?country_id=6&platform_id=3&limit=10&page=1" \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const params = new URLSearchParams({
  country_id: "6", platform_id: "3", limit: "10", page: "1",
});
const res = await fetch(`https://api.smscode.gg/v1/catalog/products?${params}`, {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v1/catalog/products",
    params={"country_id": 6, "platform_id": 3, "limit": 10, "page": 1},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

Ejemplo de Respuesta

200 OK
{
  "success": true,
  "data": [
    {
      "id": 142,
      "name": "WhatsApp Indonesia",
      "country_id": 6,
      "platform_id": 3,
      "catalog_product_id": 87,
      "available": 42,
      "price": 15000,
      "active": true
    }
  ],
  "meta": { "page": 1, "limit": 10, "count": 1 }
}
GET /catalog/exchange-rate

Devuelve la tasa de cambio USD/IDR actual usada para la conversión de divisas.

Parámetros de Consulta

NombreTipoRequeridoDescripción
pairstringNoPar de divisas (predeterminado: USD/IDR)

Ejemplo de Solicitud

curl -s "https://api.smscode.gg/v1/catalog/exchange-rate?pair=USD/IDR" \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v1/catalog/exchange-rate?pair=USD/IDR", {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v1/catalog/exchange-rate",
    params={"pair": "USD/IDR"},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

Ejemplo de Respuesta

200 OK
{
  "success": true,
  "data": {
    "pair": "USD/IDR",
    "base_currency": "USD",
    "quote_currency": "IDR",
    "rate": 16250
  }
}
GET /balance

Devuelve el saldo de cuenta del usuario autenticado.

Parámetros

Ninguno

Ejemplo de Solicitud

curl -s https://api.smscode.gg/v1/balance \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v1/balance", {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v1/balance",
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

Ejemplo de Respuesta

200 OK
{
  "success": true,
  "data": {
    "currency": "IDR",
    "balance": 500000
  }
}
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

NombreTipoRequeridoDescripción
limitintegerNoMáximo de resultados (1-100, predeterminado 20)
offsetintegerNoNúmero de resultados a omitir (predeterminado 0)
statusstringNoFiltrar por estado: ACTIVE, OTP_RECEIVED, COMPLETED, CANCELED, EXPIRED (sin distinción de mayúsculas)

Ejemplo de Solicitud

curl -s "https://api.smscode.gg/v1/orders?limit=5&status=ACTIVE" \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const params = new URLSearchParams({
  limit: "5", status: "ACTIVE", offset: "0",
});
const res = await fetch(`https://api.smscode.gg/v1/orders?${params}`, {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v1/orders",
    params={"limit": 5, "status": "ACTIVE", "offset": 0},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

Ejemplo de Respuesta

200 OK
{
  "success": true,
  "data": [
    {
      "id": 1001,
      "status": "ACTIVE",
      "created_at": "2026-02-25T10:00:00+00:00",
      "product_id": 142,
      "phone_number": "+6281234567890",
      "amount": 15000,
      "otp_code": null,
      "otp_received_at": null,
      "expires_at": "2026-02-25T10:20:00+00:00",
      "canceled_at": null,
      "failed_reason": null
    }
  ]
}
GET /orders/{id}

Devuelve un pedido individual por ID. Solo devuelve pedidos del usuario autenticado.

Parámetros de Ruta

NombreTipoRequeridoDescripción
idintegerID del pedido (parámetro de ruta)

Ejemplo de Solicitud

curl -s https://api.smscode.gg/v1/orders/1001 \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v1/orders/1001", {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v1/orders/1001",
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

Ejemplo de Respuesta

200 OK
{
  "success": true,
  "data": {
    "id": 1001,
    "status": "OTP_RECEIVED",
    "created_at": "2026-02-25T10:00:00+00:00",
    "product_id": 142,
    "phone_number": "+6281234567890",
    "amount": 15000,
    "otp_code": "123456",
    "otp_received_at": "2026-02-25T10:05:00+00:00",
    "expires_at": "2026-02-25T10:20:00+00:00",
    "canceled_at": null,
    "failed_reason": null
  }
}
GET /orders/active

Lista todos los pedidos actualmente activos (ACTIVE + OTP_RECEIVED). Úsalo para consultar actualizaciones de estado de OTP.

Parámetros

Ninguno

Ejemplo de Solicitud

curl -s https://api.smscode.gg/v1/orders/active \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v1/orders/active", {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v1/orders/active",
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

Ejemplo de Respuesta

200 OK
{
  "success": true,
  "data": [
    {
      "id": 1001,
      "status": "OTP_RECEIVED",
      "otp_code": "123456",
      "otp_message": "Your verification code is 123456",
      "otp_received_at": "2026-02-25T10:05:00+00:00",
      "expires_at": "2026-02-25T10:20:00+00:00",
      "failed_reason": null
    },
    {
      "id": 1002,
      "status": "ACTIVE",
      "otp_code": null,
      "otp_message": null,
      "otp_received_at": null,
      "expires_at": "2026-02-25T10:50:00+00:00",
      "failed_reason": null
    }
  ]
}
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

NombreTipoRequeridoDescripción
product_idintegerNoID del producto a pedir. Indica SOLO UNO: product_id O catalog_product_id, no ambos.
catalog_product_idintegerNoID de producto estable — el servidor elige la mejor oferta actual (la más barata, teniendo en cuenta la fiabilidad). Indica este o product_id.
max_priceinteger · stringNoLímite de precio opcional. v1: entero en IDR. v2: cadena decimal en USD (p. ej. "0.50").
prefer_providerstringNoCódigo de proveedor opcional a preferir cuando las ofertas empatan.
policystringNoPolí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.
quantityintegerNoCantidad 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 order by stable catalog_product_id — the server picks the best current offer
# (cheapest, reliability-aware). Optional max_price caps the price; on v1 it is an
# IDR integer, on v2 it is a USD decimal string (e.g. "0.50"). prefer_provider
# (provider code) is a soft tie-breaker. 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,"max_price":20000}'
const res = await fetch("https://api.smscode.gg/v1/orders/create", {
  method: "POST",
  headers: {
    Authorization: "Bearer YOUR_API_TOKEN",
    "Content-Type": "application/json",
    "Idempotency-Key": "unique-request-id-123",
  },
  body: JSON.stringify({ product_id: 142, quantity: 1 }),
});
const data = await res.json();
import requests

res = requests.post("https://api.smscode.gg/v1/orders/create",
    json={"product_id": 142, "quantity": 1},
    headers={
        "Authorization": "Bearer YOUR_API_TOKEN",
        "Idempotency-Key": "unique-request-id-123",
    })
data = res.json()

Ejemplo de Respuesta

200 OK
{
  "success": true,
  "data": {
    "orders": [
      {
        "id": 1002,
        "status": "ACTIVE",
        "product_id": 142,
        "catalog_product_id": 87,
        "amount": 15000,
        "phone_number": "+6281234567891",
        "otp_code": null,
        "otp_received_at": null,
        "expires_at": "2026-02-25T10:50:00+00:00",
        "failed_reason": null
      }
    ],
    "failed_count": 0
  }
}
POST /orders/cancel

Cancela un pedido activo. El costo del alquiler se reembolsa al saldo de tu cuenta.

Cuerpo de la Solicitud

NombreTipoRequeridoDescripción
idintegerID del pedido a cancelar

Ejemplo de Solicitud

curl -s -X POST https://api.smscode.gg/v1/orders/cancel \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"id":1001}'
const res = await fetch("https://api.smscode.gg/v1/orders/cancel", {
  method: "POST",
  headers: {
    Authorization: "Bearer YOUR_API_TOKEN",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ id: 1001 }),
});
const data = await res.json();
import requests

res = requests.post("https://api.smscode.gg/v1/orders/cancel",
    json={"id": 1001},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

Ejemplo de Respuesta

200 OK
{
  "success": true,
  "data": {
    "order_id": 1001,
    "status": "CANCELED",
    "refund_amount": 15000,
    "new_balance": 515000
  }
}
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.

Cuerpo de la Solicitud

NombreTipoRequeridoDescripción
idintegerID del pedido a finalizar

Ejemplo de Solicitud

curl -s -X POST https://api.smscode.gg/v1/orders/finish \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"id":1001}'
const res = await fetch("https://api.smscode.gg/v1/orders/finish", {
  method: "POST",
  headers: {
    Authorization: "Bearer YOUR_API_TOKEN",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ id: 1001 }),
});
const data = await res.json();
import requests

res = requests.post("https://api.smscode.gg/v1/orders/finish",
    json={"id": 1001},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

Ejemplo de Respuesta

200 OK
{
  "success": true,
  "data": {
    "order_id": 1001,
    "status": "COMPLETED"
  }
}
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.

Cuerpo de la Solicitud

NombreTipoRequeridoDescripción
idintegerID del pedido para reenviar SMS

Ejemplo de Solicitud

curl -s -X POST https://api.smscode.gg/v1/orders/resend \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"id":1001}'
const res = await fetch("https://api.smscode.gg/v1/orders/resend", {
  method: "POST",
  headers: {
    Authorization: "Bearer YOUR_API_TOKEN",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ id: 1001 }),
});
const data = await res.json();
import requests

res = requests.post("https://api.smscode.gg/v1/orders/resend",
    json={"id": 1001},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

Ejemplo de Respuesta

200 OK
{
  "success": true,
  "data": {
    "order_id": 1001,
    "status": "ACTIVE",
    "resent": true
  }
}
GET /webhook

Devuelve tu configuración actual de notificaciones webhook.

Parámetros

Ninguno

Ejemplo de Solicitud

curl -s https://api.smscode.gg/v1/webhook \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v1/webhook", {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v1/webhook",
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

Ejemplo de Respuesta

200 OK
{
  "success": true,
  "data": {
    "webhook_url": "https://example.com/webhook",
    "webhook_secret": "a1b2c3d4e5f6..."
  }
}
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

NombreTipoRequeridoDescripción
webhook_urlstringNoURL HTTPS para recibir eventos webhook (cadena vacía para borrar)
webhook_secretstringNoSecreto compartido para firma HMAC-SHA256 (se genera automáticamente si se omite en la primera configuración)

Se requiere al menos un campo.

Ejemplo de Solicitud

curl -s -X PATCH https://api.smscode.gg/v1/webhook \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"webhook_url":"https://example.com/webhook"}'
const res = await fetch("https://api.smscode.gg/v1/webhook", {
  method: "PATCH",
  headers: {
    Authorization: "Bearer YOUR_API_TOKEN",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ webhook_url: "https://example.com/webhook" }),
});
const data = await res.json();
import requests

res = requests.patch("https://api.smscode.gg/v1/webhook",
    json={"webhook_url": "https://example.com/webhook"},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

Ejemplo de Respuesta

200 OK
{
  "success": true,
  "data": {
    "webhook_url": "https://example.com/webhook",
    "webhook_secret": "a1b2c3d4e5f6..."
  }
}
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/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();
import requests

res = requests.post("https://api.smscode.gg/v1/webhook/test",
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

Ejemplo de Respuesta

200 OK
{
  "success": true,
  "data": {
    "status_code": 200
  }
}

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

EventoDisparador
order.otp_receivedCódigo OTP entregado al número alquilado
order.completedPedido marcado como completado (manual o por expiración)
order.expiredPedido expirado sin OTP (saldo reembolsado)
order.canceledPedido cancelado por el usuario (saldo reembolsado)

Payload

Cuerpo del POST Webhook
{
  "event": "order.otp_received",
  "timestamp": "2026-02-25T12:00:00+00:00",
  "data": {
    "order_id": 1001,
    "phone_number": "+628123456789",
    "otp_code": "1234",
    "otp_message": "Your verification code is 1234",
    "product_id": 42,
    "catalog_product_id": 87,
    "country": "Indonesia",
    "platform": "WhatsApp"
  }
}

Verificación de Firma

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:

X-Webhook-Signature: sha256={HMAC-SHA256(body, webhook_secret)}

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.

Rate Limits

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 EndpointsLímiteVentana
Catálogo (países, servicios, productos, tasa de cambio)5.000 solicitudes60 segundos
Saldo600 solicitudes60 segundos
Lectura de pedidos (listar, obtener, activos)5.000 solicitudes60 segundos
Crear pedido3.000 solicitudes60 segundos
Cancelar pedido1.000 solicitudes60 segundos
Acciones de pedido (finalizar, reenviar)1.000 solicitudes60 segundos
Configuración de webhook (obtener, actualizar)600 solicitudes60 segundos
Prueba de webhook10 solicitudes60 segundos

Códigos de Error

Las respuestas de error incluyen uno de estos códigos en error.code:

CódigoHTTPDescripción
UNAUTHORIZED401Token de la API faltante o inválido
FORBIDDEN403Acceso denegado
NOT_FOUND404Recurso no encontrado (pedido, tasa de cambio, etc.)
CONFLICT409Solicitud duplicada o conflicto de recursos
INSUFFICIENT_BALANCE409Saldo insuficiente para crear el pedido
VALIDATION_ERROR422Los parámetros de la solicitud no pasaron la validación
RATE_LIMIT_EXCEEDED429Demasiadas solicitudes (verifica el encabezado Retry-After)
INTERNAL_ERROR500Error interno del servidor
PROVIDER_ERROR422El proveedor SMS ascendente rechazó la solicitud. En fallos de creación de pedido, el error puede incluir <code>details</code>: <code>cause_counts</code> (pedidos con <code>product_id</code> heredado — un recuento agrupado por causa) o <code>attempts</code> (pedidos con <code>catalog_product_id</code> — resultados por intento), usando los valores <code>ok</code>, <code>no_numbers</code>, <code>insufficient_balance</code>, <code>price_rejected</code>, <code>provider_unavailable</code>, <code>provider_account_balance</code>, <code>provider_error</code>.
NO_OFFER_AVAILABLE422Ninguna oferta activa coincide con el producto y la política solicitados (límite de precio, disponibilidad).
CANCEL_TOO_EARLY409Pedido demasiado reciente para cancelar — espera 2 minutos
REQUEST_IN_PROGRESS409Una solicitud de creación con esta clave de idempotencia aún está en curso
IDEMPOTENCY_KEY_REUSED422Esta clave de idempotencia ya se usó con un cuerpo de solicitud diferente
SERVICE_UNAVAILABLE503Servicio 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.

Migración de v1 a v2

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/v2

Formato de Respuesta

Cada respuesta devuelve JSON con una estructura consistente. Todas las respuestas incluyen un encabezado x-request-id para depuración.

Éxito
{
  "success": true,
  "data": { ... }
}
Error
{
  "success": false,
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable message"
  }
}

Error v2: no hay tasa de cambio utilizable (503)

503 Service Unavailable · Retry-After: 60
{
  "success": false,
  "error": { "code": "FX_RATE_UNAVAILABLE", "message": "USD/IDR exchange rate is unavailable" }
}

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.

Parámetros

Ninguno

Ejemplo de Solicitud

curl -s https://api.smscode.gg/v2/catalog/countries \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v2/catalog/countries", {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v2/catalog/countries",
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

Ejemplo de Respuesta

200 OK
{
  "success": true,
  "data": [
    {
      "id": 6,
      "code": "ID",
      "name": "Indonesia",
      "dial_code": "+62",
      "emoji": "🇮🇩",
      "active": true
    }
  ]
}

Idéntico a v1 — solo cambia la ruta base (/v1/v2).

GET /catalog/services

Devuelve una lista de servicios (plataformas) disponibles. Opcionalmente filtra por país.

Parámetros de Consulta

NombreTipoRequeridoDescripción
country_idintegerNoFiltrar servicios disponibles para este país

Ejemplo de Solicitud

curl -s "https://api.smscode.gg/v2/catalog/services?country_id=6" \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v2/catalog/services?country_id=6", {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v2/catalog/services",
    params={"country_id": 6},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

Ejemplo de Respuesta

200 OK
{
  "success": true,
  "data": [
    {
      "id": 3,
      "code": "wa",
      "name": "WhatsApp",
      "active": true
    }
  ]
}

Idéntico a v1 — solo cambia la ruta base (/v1/v2).

GET /catalog/products

Devuelve una lista paginada de productos disponibles. Filtra por país y/o plataforma.

Parámetros de Consulta

NombreTipoRequeridoDescripción
country_idintegerNoFiltrar por ID de país
platform_idintegerNoFiltrar por ID de plataforma/servicio
sortstringNoOrden de clasificación: price_asc (predeterminado), price_desc, available_asc, available_desc, name_asc, name_desc
limitintegerNoResultados por página (1-10.000, predeterminado 1.000)
pageintegerNoNúmero de página (mínimo 1, predeterminado 1)

Ejemplo de Solicitud

curl -s "https://api.smscode.gg/v2/catalog/products?country_id=6&platform_id=3&limit=10&page=1" \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const params = new URLSearchParams({
  country_id: "6", platform_id: "3", limit: "10", page: "1",
});
const res = await fetch(`https://api.smscode.gg/v2/catalog/products?${params}`, {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v2/catalog/products",
    params={"country_id": 6, "platform_id": 3, "limit": 10, "page": 1},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

Ejemplo de Respuesta

200 OK
{
  "success": true,
  "data": [
    {
      "id": 142,
      "name": "WhatsApp Indonesia",
      "country_id": 6,
      "platform_id": 3,
      "catalog_product_id": 87,
      "available": 42,
      "price": { "amount": "0.9231", "currency": "USD", "canonical_amount": 15000, "canonical_currency": "IDR" },
      "active": true
    }
  ],
  "meta": { "page": 1, "limit": 10, "count": 1, "fx": { "pair": "USD/IDR", "rate": 16250, "rate_as_of": "2026-05-27T08:00:00+00:00" } }
}

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.

Ejemplo de Solicitud

curl -s "https://api.smscode.gg/v2/catalog/exchange-rate?pair=USD/IDR" \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v2/catalog/exchange-rate?pair=USD/IDR", {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v2/catalog/exchange-rate",
    params={"pair": "USD/IDR"},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

Ejemplo de Respuesta

200 OK
{
  "success": true,
  "data": { "pair": "USD/IDR", "rate": 16250, "rate_as_of": "2026-05-27T08:00:00+00:00" }
}

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.

Parámetros

Ninguno

Ejemplo de Solicitud

curl -s https://api.smscode.gg/v2/balance \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v2/balance", {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v2/balance",
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

Ejemplo de Respuesta

200 OK
{
  "success": true,
  "data": {
    "balance": { "amount": "30.77", "currency": "USD", "canonical_amount": 500000, "canonical_currency": "IDR" }
  },
  "meta": { "fx": { "pair": "USD/IDR", "rate": 16250, "rate_as_of": "2026-05-27T08:00:00+00:00" } }
}

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

NombreTipoRequeridoDescripción
limitintegerNoMáximo de resultados (1-100, predeterminado 20)
offsetintegerNoNúmero de resultados a omitir (predeterminado 0)
statusstringNoFiltrar por estado: ACTIVE, OTP_RECEIVED, COMPLETED, CANCELED, EXPIRED (sin distinción de mayúsculas)

Ejemplo de Solicitud

curl -s "https://api.smscode.gg/v2/orders?limit=5&status=ACTIVE" \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const params = new URLSearchParams({
  limit: "5", status: "ACTIVE", offset: "0",
});
const res = await fetch(`https://api.smscode.gg/v2/orders?${params}`, {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v2/orders",
    params={"limit": 5, "status": "ACTIVE", "offset": 0},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

Ejemplo de Respuesta

200 OK
{
  "success": true,
  "data": [
    {
      "id": 1001,
      "status": "ACTIVE",
      "created_at": "2026-02-25T10:00:00+00:00",
      "product_id": 142,
      "phone_number": "+6281234567890",
      "amount": { "amount": "0.9231", "currency": "USD", "canonical_amount": 15000, "canonical_currency": "IDR" },
      "otp_code": null,
      "otp_received_at": null,
      "expires_at": "2026-02-25T10:20:00+00:00",
      "canceled_at": null,
      "failed_reason": null
    }
  ],
  "meta": { "fx": { "pair": "USD/IDR", "rate": 16250, "rate_as_of": "2026-05-27T08:00:00+00:00" } }
}

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.

Parámetros de Ruta

NombreTipoRequeridoDescripción
idintegerID del pedido (parámetro de ruta)

Ejemplo de Solicitud

curl -s https://api.smscode.gg/v2/orders/1001 \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v2/orders/1001", {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v2/orders/1001",
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

Ejemplo de Respuesta

200 OK
{
  "success": true,
  "data": {
    "id": 1001,
    "status": "OTP_RECEIVED",
    "created_at": "2026-02-25T10:00:00+00:00",
    "product_id": 142,
    "phone_number": "+6281234567890",
    "amount": { "amount": "0.9231", "currency": "USD", "canonical_amount": 15000, "canonical_currency": "IDR" },
    "otp_code": "123456",
    "otp_received_at": "2026-02-25T10:05:00+00:00",
    "expires_at": "2026-02-25T10:20:00+00:00",
    "canceled_at": null,
    "failed_reason": null
  },
  "meta": { "fx": { "pair": "USD/IDR", "rate": 16250, "rate_as_of": "2026-05-27T08:00:00+00:00" } }
}

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.

Parámetros

Ninguno

Ejemplo de Solicitud

curl -s https://api.smscode.gg/v2/orders/active \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v2/orders/active", {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v2/orders/active",
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

Ejemplo de Respuesta

200 OK
{
  "success": true,
  "data": [
    {
      "id": 1001,
      "status": "OTP_RECEIVED",
      "otp_code": "123456",
      "otp_message": "Your verification code is 123456",
      "otp_received_at": "2026-02-25T10:05:00+00:00",
      "expires_at": "2026-02-25T10:20:00+00:00",
      "failed_reason": null
    },
    {
      "id": 1002,
      "status": "ACTIVE",
      "otp_code": null,
      "otp_message": null,
      "otp_received_at": null,
      "expires_at": "2026-02-25T10:50:00+00:00",
      "failed_reason": null
    }
  ]
}

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

NombreTipoRequeridoDescripción
product_idintegerNoID del producto a pedir. Indica SOLO UNO: product_id O catalog_product_id, no ambos.
catalog_product_idintegerNoID de producto estable — el servidor elige la mejor oferta actual (la más barata, teniendo en cuenta la fiabilidad). Indica este o product_id.
max_priceinteger · stringNoLímite de precio opcional. v1: entero en IDR. v2: cadena decimal en USD (p. ej. "0.50").
prefer_providerstringNoCódigo de proveedor opcional a preferir cuando las ofertas empatan.
policystringNoPolí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.
quantityintegerNoCantidad 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/v2/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 order by stable catalog_product_id — the server picks the best current offer
# (cheapest, reliability-aware). Optional max_price caps the price; on v1 it is an
# IDR integer, on v2 it is a USD decimal string (e.g. "0.50"). prefer_provider
# (provider code) is a soft tie-breaker. Pass EITHER product_id OR catalog_product_id.
curl -s -X POST https://api.smscode.gg/v2/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,"max_price":20000}'
const res = await fetch("https://api.smscode.gg/v2/orders/create", {
  method: "POST",
  headers: {
    Authorization: "Bearer YOUR_API_TOKEN",
    "Content-Type": "application/json",
    "Idempotency-Key": "unique-request-id-123",
  },
  body: JSON.stringify({ product_id: 142, quantity: 1 }),
});
const data = await res.json();
import requests

res = requests.post("https://api.smscode.gg/v2/orders/create",
    json={"product_id": 142, "quantity": 1},
    headers={
        "Authorization": "Bearer YOUR_API_TOKEN",
        "Idempotency-Key": "unique-request-id-123",
    })
data = res.json()

Ejemplo de Respuesta

200 OK
{
  "success": true,
  "data": {
    "orders": [
      {
        "id": 1002,
        "status": "ACTIVE",
        "product_id": 142,
        "catalog_product_id": 87,
        "amount": { "amount": "0.9231", "currency": "USD", "canonical_amount": 15000, "canonical_currency": "IDR" },
        "phone_number": "+6281234567891",
        "otp_code": null,
        "otp_received_at": null,
        "expires_at": "2026-02-25T10:50:00+00:00",
        "failed_reason": null
      }
    ],
    "failed_count": 0
  },
  "meta": { "fx": { "pair": "USD/IDR", "rate": 16250, "rate_as_of": "2026-05-27T08:00:00+00:00" } }
}

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.

Cuerpo de la Solicitud

NombreTipoRequeridoDescripción
idintegerID del pedido a cancelar

Ejemplo de Solicitud

curl -s -X POST https://api.smscode.gg/v2/orders/cancel \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"id":1001}'
const res = await fetch("https://api.smscode.gg/v2/orders/cancel", {
  method: "POST",
  headers: {
    Authorization: "Bearer YOUR_API_TOKEN",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ id: 1001 }),
});
const data = await res.json();
import requests

res = requests.post("https://api.smscode.gg/v2/orders/cancel",
    json={"id": 1001},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

Ejemplo de Respuesta

200 OK
{
  "success": true,
  "data": {
    "order_id": 1001,
    "status": "CANCELED",
    "refund_amount": { "amount": "0.9231", "currency": "USD", "canonical_amount": 15000, "canonical_currency": "IDR" },
    "new_balance": { "amount": "31.69", "currency": "USD", "canonical_amount": 515000, "canonical_currency": "IDR" }
  },
  "meta": { "fx": { "pair": "USD/IDR", "rate": 16250, "rate_as_of": "2026-05-27T08:00:00+00:00" } }
}

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.

Cuerpo de la Solicitud

NombreTipoRequeridoDescripción
idintegerID del pedido a finalizar

Ejemplo de Solicitud

curl -s -X POST https://api.smscode.gg/v2/orders/finish \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"id":1001}'
const res = await fetch("https://api.smscode.gg/v2/orders/finish", {
  method: "POST",
  headers: {
    Authorization: "Bearer YOUR_API_TOKEN",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ id: 1001 }),
});
const data = await res.json();
import requests

res = requests.post("https://api.smscode.gg/v2/orders/finish",
    json={"id": 1001},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

Ejemplo de Respuesta

200 OK
{
  "success": true,
  "data": {
    "order_id": 1001,
    "status": "COMPLETED"
  }
}

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.

Cuerpo de la Solicitud

NombreTipoRequeridoDescripción
idintegerID del pedido para reenviar SMS

Ejemplo de Solicitud

curl -s -X POST https://api.smscode.gg/v2/orders/resend \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"id":1001}'
const res = await fetch("https://api.smscode.gg/v2/orders/resend", {
  method: "POST",
  headers: {
    Authorization: "Bearer YOUR_API_TOKEN",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ id: 1001 }),
});
const data = await res.json();
import requests

res = requests.post("https://api.smscode.gg/v2/orders/resend",
    json={"id": 1001},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

Ejemplo de Respuesta

200 OK
{
  "success": true,
  "data": {
    "order_id": 1001,
    "status": "ACTIVE",
    "resent": true
  }
}

Idéntico a v1 — solo cambia la ruta base (/v1/v2).

GET /webhook

Devuelve tu configuración actual de notificaciones webhook.

Parámetros

Ninguno

Ejemplo de Solicitud

curl -s https://api.smscode.gg/v2/webhook \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v2/webhook", {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v2/webhook",
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

Ejemplo de Respuesta

200 OK
{
  "success": true,
  "data": {
    "webhook_url": "https://example.com/webhook",
    "webhook_secret": "a1b2c3d4e5f6..."
  }
}

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

NombreTipoRequeridoDescripción
webhook_urlstringNoURL HTTPS para recibir eventos webhook (cadena vacía para borrar)
webhook_secretstringNoSecreto compartido para firma HMAC-SHA256 (se genera automáticamente si se omite en la primera configuración)

Se requiere al menos un campo.

Ejemplo de Solicitud

curl -s -X PATCH https://api.smscode.gg/v2/webhook \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"webhook_url":"https://example.com/webhook"}'
const res = await fetch("https://api.smscode.gg/v2/webhook", {
  method: "PATCH",
  headers: {
    Authorization: "Bearer YOUR_API_TOKEN",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ webhook_url: "https://example.com/webhook" }),
});
const data = await res.json();
import requests

res = requests.patch("https://api.smscode.gg/v2/webhook",
    json={"webhook_url": "https://example.com/webhook"},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

Ejemplo de Respuesta

200 OK
{
  "success": true,
  "data": {
    "webhook_url": "https://example.com/webhook",
    "webhook_secret": "a1b2c3d4e5f6..."
  }
}

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();
import requests

res = requests.post("https://api.smscode.gg/v2/webhook/test",
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

Ejemplo de Respuesta

200 OK
{
  "success": true,
  "data": {
    "status_code": 200
  }
}

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

EventoDisparador
order.otp_receivedCódigo OTP entregado al número alquilado
order.completedPedido marcado como completado (manual o por expiración)
order.expiredPedido expirado sin OTP (saldo reembolsado)
order.canceledPedido cancelado por el usuario (saldo reembolsado)

Payload

Cuerpo del POST Webhook
{
  "event": "order.otp_received",
  "timestamp": "2026-02-25T12:00:00+00:00",
  "data": {
    "order_id": 1001,
    "phone_number": "+628123456789",
    "otp_code": "1234",
    "otp_message": "Your verification code is 1234",
    "product_id": 42,
    "catalog_product_id": 87,
    "country": "Indonesia",
    "platform": "WhatsApp"
  }
}

Verificación de Firma

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:

X-Webhook-Signature: sha256={HMAC-SHA256(body, webhook_secret)}

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.

Rate Limits

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 EndpointsLímiteVentana
Catálogo (países, servicios, productos, tasa de cambio)5.000 solicitudes60 segundos
Saldo600 solicitudes60 segundos
Lectura de pedidos (listar, obtener, activos)5.000 solicitudes60 segundos
Crear pedido3.000 solicitudes60 segundos
Cancelar pedido1.000 solicitudes60 segundos
Acciones de pedido (finalizar, reenviar)1.000 solicitudes60 segundos
Configuración de webhook (obtener, actualizar)600 solicitudes60 segundos
Prueba de webhook10 solicitudes60 segundos

Códigos de Error

Las respuestas de error incluyen uno de estos códigos en error.code:

CódigoHTTPDescripción
UNAUTHORIZED401Token de la API faltante o inválido
FORBIDDEN403Acceso denegado
NOT_FOUND404Recurso no encontrado (pedido, tasa de cambio, etc.)
CONFLICT409Solicitud duplicada o conflicto de recursos
INSUFFICIENT_BALANCE409Saldo insuficiente para crear el pedido
VALIDATION_ERROR422Los parámetros de la solicitud no pasaron la validación
RATE_LIMIT_EXCEEDED429Demasiadas solicitudes (verifica el encabezado Retry-After)
INTERNAL_ERROR500Error interno del servidor
PROVIDER_ERROR422El proveedor SMS ascendente rechazó la solicitud. En fallos de creación de pedido, el error puede incluir <code>details</code>: <code>cause_counts</code> (pedidos con <code>product_id</code> heredado — un recuento agrupado por causa) o <code>attempts</code> (pedidos con <code>catalog_product_id</code> — resultados por intento), usando los valores <code>ok</code>, <code>no_numbers</code>, <code>insufficient_balance</code>, <code>price_rejected</code>, <code>provider_unavailable</code>, <code>provider_account_balance</code>, <code>provider_error</code>.
NO_OFFER_AVAILABLE422Ninguna oferta activa coincide con el producto y la política solicitados (límite de precio, disponibilidad).
CANCEL_TOO_EARLY409Pedido demasiado reciente para cancelar — espera 2 minutos
REQUEST_IN_PROGRESS409Una solicitud de creación con esta clave de idempotencia aún está en curso
IDEMPOTENCY_KEY_REUSED422Esta clave de idempotencia ya se usó con un cuerpo de solicitud diferente
SERVICE_UNAVAILABLE503Servicio temporalmente no disponible (mantenimiento)
FX_RATE_UNAVAILABLE503Tasa 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.

Aspectov1 · IDRv2 · USD
Campos monetariosIDR entero, p. ej. 15000Objeto monetario { amount, currency, canonical_amount, canonical_currency }
meta.fxAusenteObligatorio en toda respuesta monetaria
DivisaIDRUSD (fijo en el código)
FX_RATE_UNAVAILABLENuevo 503 + Retry-After cuando no hay una tasa utilizable
PrecisiónTotales 2 decimales, precios/reembolsos 4 decimales, redondeo hacia arriba en positivos
GET /catalog/exchange-rate{pair, base_currency, quote_currency, rate}; respeta ?pair{pair, rate, rate_as_of}; ?pair ignorado (solo USD/IDR)

Ejemplos en paralelo

GET/balance
v1
{
  "success": true,
  "data": {
    "currency": "IDR",
    "balance": 500000
  }
}
v2
{
  "success": true,
  "data": {
    "balance": { "amount": "30.77", "currency": "USD", "canonical_amount": 500000, "canonical_currency": "IDR" }
  },
  "meta": { "fx": { "pair": "USD/IDR", "rate": 16250, "rate_as_of": "2026-05-27T08:00:00+00:00" } }
}
GET/catalog/products
v1
{
  "success": true,
  "data": [
    {
      "id": 142,
      "name": "WhatsApp Indonesia",
      "country_id": 6,
      "platform_id": 3,
      "catalog_product_id": 87,
      "available": 42,
      "price": 15000,
      "active": true
    }
  ],
  "meta": { "page": 1, "limit": 10, "count": 1 }
}
v2
{
  "success": true,
  "data": [
    {
      "id": 142,
      "name": "WhatsApp Indonesia",
      "country_id": 6,
      "platform_id": 3,
      "catalog_product_id": 87,
      "available": 42,
      "price": { "amount": "0.9231", "currency": "USD", "canonical_amount": 15000, "canonical_currency": "IDR" },
      "active": true
    }
  ],
  "meta": { "page": 1, "limit": 10, "count": 1, "fx": { "pair": "USD/IDR", "rate": 16250, "rate_as_of": "2026-05-27T08:00:00+00:00" } }
}

catalog_product_id es la identidad ESTABLE del producto (una por país + plataforma) — es seguro guardarla en tus sistemas Y además se puede pedir directamente: haz el pedido con catalog_product_id (más los opcionales max_price y prefer_provider) y el servidor resuelve la oferta activa por ti. La vía mediante el id por oferta (product_id) sigue soportada y es retrocompatible — ese id es volátil y cambia cada vez que se mueve un nivel de precio del proveedor, así que la recomendación de volver a consultar el catálogo antes de pedir solo aplica a esa vía.

POST/orders/cancel
v1
{
  "success": true,
  "data": {
    "order_id": 1001,
    "status": "CANCELED",
    "refund_amount": 15000,
    "new_balance": 515000
  }
}
v2
{
  "success": true,
  "data": {
    "order_id": 1001,
    "status": "CANCELED",
    "refund_amount": { "amount": "0.9231", "currency": "USD", "canonical_amount": 15000, "canonical_currency": "IDR" },
    "new_balance": { "amount": "31.69", "currency": "USD", "canonical_amount": 515000, "canonical_currency": "IDR" }
  },
  "meta": { "fx": { "pair": "USD/IDR", "rate": 16250, "rate_as_of": "2026-05-27T08:00:00+00:00" } }
}

Lista de verificación de migración

  1. Cambia la ruta base /v1/v2.
  2. Parsea los campos monetarios como objetos — lee amount como una cadena decimal; currency es "USD".
  3. 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.
  4. Maneja el nuevo FX_RATE_UNAVAILABLE (503) — reintenta después de Retry-After. v1 nunca devuelve esto.