Saltar al contenido
smsbulk

API de SMS masivos v1

Una pequeña API REST para enviar SMS masivos desde tu propio software: entrada y salida en JSON, claves Bearer, los mismos precios y verificaciones que la consola, y webhooks firmados para informes de entrega. URL base https://smsbulk.cc/rest/v1

Resumen

Envía hasta 8,000 números por solicitud, cada uno con sus propios valores de fusión, y la API calcula el precio para cada destinatario con su texto fusionado, exactamente como la consola. Solicita primero una cotización con el mismo cuerpo para conocer el costo; nada se envía ni se cobra con una cotización. Los tiempos son ISO 8601 en UTC, el dinero es una cadena en dólares estadounidenses con cinco decimales.

curl https://smsbulk.cc/rest/v1/send \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "recipients": [
      { "to": "+14155550142", "fields": { "name": "Maria" } },
      { "to": "+447400123456", "fields": { "name": "Tom" } }
    ],
    "text": "Hi {{name}}, your order ships today.",
    "client_ref": "order-batch-17"
  }'

Autenticación

Crea una clave en la consola en API y webhooks; las claves se activan después de tu primera recarga acreditada. Envíala en cada solicitud como un token Bearer. Una clave se muestra una vez; puedes tener hasta 5 claves y revocar cualquiera de ellas al instante.

Authorization: Bearer smsb_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Tarifas

Público, no se necesita clave. El precio por segmento de SMS para cada destino, o para un código ISO.

GEThttps://smsbulk.cc/rest/v1/rates

GEThttps://smsbulk.cc/rest/v1/rates/{ISO}

{
  "country": "US",
  "name": "United States",
  "dial_code": "+1",
  "rate": "0.00485",
  "currency": "USD",
  "per": "message segment"
}

Crédito

GEThttps://smsbulk.cc/rest/v1/credit

{ "credit": "125.40000", "currency": "USD", "account": "A418820037715" }

Cotización

Calcula el costo de un envío sin enviarlo. Toma el mismo cuerpo que Enviar.

POSThttps://smsbulk.cc/rest/v1/quote

CampoSignificado
toUn número o una lista. El formato internacional con + es el más seguro; los números locales requieren default_country.
recipientsEn lugar de to: una lista de { to, fields } donde fields llena campos de fusión como {{name}}.
textEl mensaje, hasta 2,000 caracteres y 10 segmentos después de la fusión.
default_countryCódigo ISO usado para números escritos sin +, como GB. Recurre al valor predeterminado guardado en Configuración.
opt_outAñade tu texto de exclusión guardado como última línea (guárdalo primero en Configuración).
{
  "recipients": 2,
  "messages": 2,
  "segments": { "min": 1, "max": 1 },
  "encoding": "GSM-7",
  "total": "0.01240",
  "currency": "USD",
  "credit": "125.40000",
  "credit_after": "125.38760",
  "countries": [
    { "country": "US", "recipients": 1, "rate": "0.00485", "segments": 1, "subtotal": "0.00485" },
    { "country": "GB", "recipients": 1, "rate": "0.00755", "segments": 1, "subtotal": "0.00755" }
  ],
  "left_out": []
}

Enviar

Crea una campaña, deduce su total de tu crédito y pone los mensajes en cola. Responde con 201 y la campaña. Los números que no se pueden enviar se listan en left_out y nunca se cobran.

POSThttps://smsbulk.cc/rest/v1/send

CampoSignificado
to / recipients / text / default_country / opt_outComo en Quote.
send_atHora ISO 8601 para comenzar, de 5 minutos a 90 días adelante. Omite para enviar ahora.
nameTu etiqueta para la campaña, hasta 80 caracteres.
client_refTu propia referencia, hasta 64 caracteres, devuelta en cada mensaje y webhook.
HTTP/1.1 201 Created
{
  "campaign": {
    "id": "C507318264409",
    "name": null,
    "status": "sending",
    "source": "api",
    "test": false,
    "recipients": 2,
    "cost": "0.01240",
    "refunded": "0.00000",
    "net_cost": "0.01240",
    "currency": "USD",
    "send_at": null,
    "created_at": "2026-11-07T14:02:11.000Z",
    "started_at": "2026-11-07T14:02:11.000Z",
    "finished_at": null,
    "client_ref": "order-batch-17"
  },
  "messages": 2,
  "left_out": []
}

Campañas

GEThttps://smsbulk.cc/rest/v1/campaigns?limit=25&before={id}

GEThttps://smsbulk.cc/rest/v1/campaigns/{id}

GEThttps://smsbulk.cc/rest/v1/campaigns/{id}/messages?status=failed&limit=100&after={message id}

Una sola campaña incluye recuentos por estado: en cola, enviado, entregado, fallido y cancelado. Listas de mensajes con after y has_more; hasta 1,000 por página.

Mensajes

GEThttps://smsbulk.cc/rest/v1/messages/{id}

GEThttps://smsbulk.cc/rest/v1/messages?to=%2B14155550142&status=delivered&client_ref=order-batch-17&limit=50

{
  "id": "M220947781305",
  "campaign": "C507318264409",
  "to": "+14155550142",
  "country": "US",
  "segments": 1,
  "encoding": "GSM-7",
  "price": "0.00485",
  "status": "delivered",
  "reason": null,
  "reason_text": null,
  "refunded": false,
  "client_ref": "order-batch-17",
  "created_at": "2026-11-07T14:02:11.000Z",
  "sent_at": "2026-11-07T14:02:12.000Z",
  "finished_at": "2026-11-07T14:02:16.000Z"
}

Cancelar una campaña programada

POSThttps://smsbulk.cc/rest/v1/campaigns/{id}/cancel

Funciona hasta la hora de inicio. El costo total regresa a tu crédito en una línea de registro y el estado de la campaña se cancela.

Webhooks

Establece una URL de endpoint en la consola. Enviamos JSON para message.delivered, message.failed y campaign.done, más prueba cuando presionas Enviar evento de prueba. Cada evento lleva cuatro encabezados, y cualquier respuesta 2xx dentro de 10 segundos cuenta como recibida.

CampoSignificado
Smsbulk-EventNombre del evento, como message.delivered.
Smsbulk-DeliveryID de entrega, el mismo en cada reintento de un evento.
Smsbulk-TimestampSegundos Unix cuando se firmó este intento.
Smsbulk-Signaturesha256= seguido del HMAC-SHA256 en base64 de timestamp, un salto de línea y el cuerpo sin procesar, cifrado con tu secreto sbw_.

La entrega se intenta 8 veces: de inmediato, luego después de 1 minuto, 5 minutos, 15 minutos, 1 hora, 3 horas, 6 horas y 12 horas. Cada intento se muestra en el registro de entrega de la consola con su estado HTTP y el inicio de tu respuesta.

{
  "id": "dlv_3f9a1c07b2e44d58a6c1",
  "event": "message.failed",
  "created": "2026-11-07T14:02:16.000Z",
  "data": {
    "message": "M220947781305",
    "campaign": "C507318264409",
    "to": "+447400123456",
    "country": "GB",
    "status": "failed",
    "reason": "not_in_service",
    "segments": 1,
    "price": "0.00755",
    "client_ref": "order-batch-17"
  }
}

Verificar la firma

import crypto from "node:crypto";

// raw = the request body exactly as received (string), secret = your sbw_ signing secret
export function verify(raw, headers, secret) {
  const ts = headers["smsbulk-timestamp"];
  const want = "sha256=" + crypto.createHmac("sha256", secret).update(ts + "\n" + raw).digest("base64");
  const got = headers["smsbulk-signature"] || "";
  const fresh = Math.abs(Date.now() / 1000 - Number(ts)) < 300;
  return fresh && got.length === want.length && crypto.timingSafeEqual(Buffer.from(got), Buffer.from(want));
}

Errores y límites

Los errores usan el estado HTTP y un cuerpo JSON: {"error":{"code":"insufficient_credit","message":"..."}}. No se cobra nada cuando falla una solicitud.

CampoSignificado
400 bad_jsonEl cuerpo no es JSON.
401 unauthorizedClave faltante, malformada o revocada.
402 insufficient_creditEl total supera tu saldo. Recarga fondos en la consola.
404 not_foundNo existe ese endpoint, campaña, mensaje o código ISO en esta cuenta.
409 not_scheduledSe solicitó cancelar una campaña que ya comenzó.
422 text_missing, text_too_long, too_many_segmentsMensaje vacío, más de 2,000 caracteres o más de 10 segmentos para un destinatario.
422 no_recipients, too_many, recipients_missingNingún número válido, más de 8,000 números, o falta el campo 'to' o 'recipients'.
422 schedule_range, bad_send_atsend_at fuera del rango de 5 minutos a 90 días, o no es una hora válida.
422 opt_out_unsetopt_out es true pero no hay texto guardado en Configuración.
429 rate_limitedMás de 300 solicitudes por minuto con esta clave.

Cada respuesta incluye X-RateLimit-Limit, X-RateLimit-Remaining y X-RateLimit-Reset.

Códigos excluidos

Los números eliminados antes del cálculo de precios se devuelven en left_out con su posición en tu lista, el valor enviado, un código y la razón clara.

CampoSignificado
no_country_codeSin + y sin país predeterminado.
too_short / too_longMenos de 8 o más de 15 dígitos.
not_a_numberLetras o símbolos que no son parte de un número.
nanp_lengthUn número +1 sin once dígitos.
not_coveredUn destino fuera de nuestra lista.
duplicateEl mismo número apareció antes en la solicitud.
suppressedEl número está en tu lista de no enviar.

Estados y razones

CampoSignificado
queuedEn espera para salir de nuestra cola.
sentEntregado a la red, esperando el informe de entrega. La mayoría llega en minutos; algunas redes tardan hasta 24 horas.
deliveredLa red confirmó que el teléfono lo recibió.
failedNo entregado. El precio fue reembolsado a tu saldo.
canceledNo enviado porque cancelaste. Reembolsado.
CampoSignificado
network_errorError de red
switched_offTeléfono apagado o sin cobertura
not_in_serviceNúmero no en servicio
blockedBloqueado por la red o el destinatario
landlineLínea fija o número que no puede recibir SMS
content_filterRechazado por el filtro de contenido de la red
expiredExpirado: sin informe antes del límite de tiempo de la red
unknownRespuesta de red desconocida
canceledCancelado antes del envío

¿Preguntas sobre la API? Abre un ticket en la consola o lee las FAQ.

Documentación de la API de SMS masivos v1: Enviar, Cotizar, Webhooks | SMSBulk