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:
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:
⟩Formato de Respuesta
Cada respuesta devuelve JSON con una estructura consistente. Todas las respuestas incluyen un encabezado x-request-id para depuración.
{
"success": true,
"data": { ... }
} {
"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.
/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
{
"success": true,
"data": [
{
"id": 6,
"code": "ID",
"name": "Indonesia",
"dial_code": "+62",
"emoji": "🇮🇩",
"active": true
}
]
} /catalog/services Devuelve una lista de servicios (plataformas) disponibles. Opcionalmente filtra por país.
Parámetros de Consulta
| Nombre | Tipo | Requerido | Descripción |
|---|---|---|---|
country_id | integer | No | Filtrar 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
{
"success": true,
"data": [
{
"id": 3,
"code": "wa",
"name": "WhatsApp",
"active": true
}
]
} /catalog/products Devuelve una lista paginada de productos disponibles. Filtra por país y/o plataforma.
Parámetros de Consulta
| Nombre | Tipo | Requerido | Descripción |
|---|---|---|---|
country_id | integer | No | Filtrar por ID de país |
platform_id | integer | No | Filtrar por ID de plataforma/servicio |
sort | string | No | Orden de clasificación: price_asc (predeterminado), price_desc, available_asc, available_desc, name_asc, name_desc |
limit | integer | No | Resultados por página (1-10.000, predeterminado 1.000) |
page | integer | No | Nú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
{
"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 }
} /catalog/exchange-rate Devuelve la tasa de cambio USD/IDR actual usada para la conversión de divisas.
Parámetros de Consulta
| Nombre | Tipo | Requerido | Descripción |
|---|---|---|---|
pair | string | No | Par 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
{
"success": true,
"data": {
"pair": "USD/IDR",
"base_currency": "USD",
"quote_currency": "IDR",
"rate": 16250
}
} /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
{
"success": true,
"data": {
"currency": "IDR",
"balance": 500000
}
} /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) |
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
{
"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
}
]
} /orders/{id} Devuelve un pedido individual por ID. Solo devuelve pedidos del usuario autenticado.
Parámetros de Ruta
| Nombre | Tipo | Requerido | Descripción |
|---|---|---|---|
id | integer | Sí | ID 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
{
"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
}
} /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
{
"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
}
]
} /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 del producto a pedir. Indica SOLO UNO: product_id O catalog_product_id, no ambos. |
catalog_product_id | integer | No | ID 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_price | integer · string | No | Límite de precio opcional. v1: entero en IDR. v2: cadena decimal en USD (p. ej. "0.50"). |
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 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
{
"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
}
} /orders/cancel Cancela un pedido activo. El costo del alquiler se reembolsa al saldo de tu cuenta.
Cuerpo de la Solicitud
| Nombre | Tipo | Requerido | Descripción |
|---|---|---|---|
id | integer | Sí | ID 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
{
"success": true,
"data": {
"order_id": 1001,
"status": "CANCELED",
"refund_amount": 15000,
"new_balance": 515000
}
} /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
| Nombre | Tipo | Requerido | Descripción |
|---|---|---|---|
id | integer | Sí | ID 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
{
"success": true,
"data": {
"order_id": 1001,
"status": "COMPLETED"
}
} /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
| Nombre | Tipo | Requerido | Descripción |
|---|---|---|---|
id | integer | Sí | ID 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
{
"success": true,
"data": {
"order_id": 1001,
"status": "ACTIVE",
"resent": true
}
} /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
{
"success": true,
"data": {
"webhook_url": "https://example.com/webhook",
"webhook_secret": "a1b2c3d4e5f6..."
}
} /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) |
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
{
"success": true,
"data": {
"webhook_url": "https://example.com/webhook",
"webhook_secret": "a1b2c3d4e5f6..."
}
} /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
{
"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
| Evento | Disparador |
|---|---|
order.otp_received | Código OTP entregado al número alquilado |
order.completed | Pedido marcado como completado (manual o por expiración) |
order.expired | Pedido expirado sin OTP (saldo reembolsado) |
order.canceled | Pedido cancelado por el usuario (saldo reembolsado) |
Payload
{
"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:
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 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 <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_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.
⟩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:
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:
⟩Formato de Respuesta
Cada respuesta devuelve JSON con una estructura consistente. Todas las respuestas incluyen un encabezado x-request-id para depuración.
{
"success": true,
"data": { ... }
} {
"success": false,
"error": {
"code": "ERROR_CODE",
"message": "Human-readable message"
}
} Error v2: no hay tasa de cambio utilizable (503)
{
"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.
/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
{
"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).
/catalog/services Devuelve una lista de servicios (plataformas) disponibles. Opcionalmente filtra por país.
Parámetros de Consulta
| Nombre | Tipo | Requerido | Descripción |
|---|---|---|---|
country_id | integer | No | Filtrar 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
{
"success": true,
"data": [
{
"id": 3,
"code": "wa",
"name": "WhatsApp",
"active": true
}
]
} Idéntico a v1 — solo cambia la ruta base (/v1 → /v2).
/catalog/products Devuelve una lista paginada de productos disponibles. Filtra por país y/o plataforma.
Parámetros de Consulta
| Nombre | Tipo | Requerido | Descripción |
|---|---|---|---|
country_id | integer | No | Filtrar por ID de país |
platform_id | integer | No | Filtrar por ID de plataforma/servicio |
sort | string | No | Orden de clasificación: price_asc (predeterminado), price_desc, available_asc, available_desc, name_asc, name_desc |
limit | integer | No | Resultados por página (1-10.000, predeterminado 1.000) |
page | integer | No | Nú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
{
"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.
/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
{
"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.
/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
{
"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.
/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) |
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
{
"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.
/orders/{id} Devuelve un pedido individual por ID. Solo devuelve pedidos del usuario autenticado.
Parámetros de Ruta
| Nombre | Tipo | Requerido | Descripción |
|---|---|---|---|
id | integer | Sí | ID 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
{
"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.
/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
{
"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).
/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 del producto a pedir. Indica SOLO UNO: product_id O catalog_product_id, no ambos. |
catalog_product_id | integer | No | ID 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_price | integer · string | No | Límite de precio opcional. v1: entero en IDR. v2: cadena decimal en USD (p. ej. "0.50"). |
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/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
{
"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.
/orders/cancel Cancela un pedido activo. El costo del alquiler se reembolsa al saldo de tu cuenta.
Cuerpo de la Solicitud
| Nombre | Tipo | Requerido | Descripción |
|---|---|---|---|
id | integer | Sí | ID 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
{
"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.
/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
| Nombre | Tipo | Requerido | Descripción |
|---|---|---|---|
id | integer | Sí | ID 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
{
"success": true,
"data": {
"order_id": 1001,
"status": "COMPLETED"
}
} Idéntico a v1 — solo cambia la ruta base (/v1 → /v2).
/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
| Nombre | Tipo | Requerido | Descripción |
|---|---|---|---|
id | integer | Sí | ID 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
{
"success": true,
"data": {
"order_id": 1001,
"status": "ACTIVE",
"resent": true
}
} Idéntico a v1 — solo cambia la ruta base (/v1 → /v2).
/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
{
"success": true,
"data": {
"webhook_url": "https://example.com/webhook",
"webhook_secret": "a1b2c3d4e5f6..."
}
} Idéntico a v1 — solo cambia la ruta base (/v1 → /v2).
/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) |
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
{
"success": true,
"data": {
"webhook_url": "https://example.com/webhook",
"webhook_secret": "a1b2c3d4e5f6..."
}
} Idéntico a v1 — solo cambia la ruta base (/v1 → /v2).
/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
{
"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
| Evento | Disparador |
|---|---|
order.otp_received | Código OTP entregado al número alquilado |
order.completed | Pedido marcado como completado (manual o por expiración) |
order.expired | Pedido expirado sin OTP (saldo reembolsado) |
order.canceled | Pedido cancelado por el usuario (saldo reembolsado) |
Payload
{
"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:
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 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 <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_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. |
⟩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.
| Aspecto | v1 · IDR | v2 · USD |
|---|---|---|
| Campos monetarios | IDR entero, p. ej. 15000 | Objeto monetario { amount, currency, canonical_amount, canonical_currency } |
meta.fx | Ausente | Obligatorio en toda respuesta monetaria |
| Divisa | IDR | USD (fijo en el código) |
FX_RATE_UNAVAILABLE | — | Nuevo 503 + Retry-After cuando no hay una tasa utilizable |
| Precisión | — | Totales 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
{
"success": true,
"data": {
"currency": "IDR",
"balance": 500000
}
}{
"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" } }
}{
"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 }
}{
"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.
{
"success": true,
"data": {
"order_id": 1001,
"status": "CANCELED",
"refund_amount": 15000,
"new_balance": 515000
}
}{
"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
- Cambia la ruta base
/v1→/v2. - Parsea los campos monetarios como objetos — lee
amountcomo una cadena decimal;currencyes"USD". - Para la conciliación del libro mayor usa
canonical_amount(IDR exacto); elamounten USD es una proyección en tiempo de renderizado y laratese revela una vez enmeta.fx. - Maneja el nuevo
FX_RATE_UNAVAILABLE(503) — reintenta después deRetry-After. v1 nunca devuelve esto.