Documentación de la API de SMS para enviar desde tu propio código

Una pequeña API REST en la misma cuenta, precios y saldo que el espacio de trabajo: envía un mensaje o un lote de hasta 5,000 números, estima el costo primero, sigue cada mensaje y obtén eventos de entrega en tu URL de webhook.

URL base: https://sendsmsonline.cc/api/v1

Inicio rápido

  1. 1. Crea una clave. En el espacio de trabajo, abre API y webhooks. Las claves se activan después de tu primera recarga; cada clave se muestra una vez, así que cópiala o guárdala como un archivo de texto.
  2. 2. Verifica el costo. Envía el mismo cuerpo a /costs para ver los destinatarios, segmentos y el total sin enviar.
  3. 3. Envía. Envíalo a /sends. La respuesta incluye el ID del envío; los mensajes se envían de inmediato o en send_at.
curl https://sendsmsonline.cc/api/v1/sends \
  -H "Authorization: Bearer $SSO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "recipients": [ { "to": "+447700900461", "fields": { "first_name": "Tom" } }, { "to": "+12015550142", "fields": { "first_name": "Ava" } } ], "text": "Hi {{first_name}}, your table is ready at 7:30.", "name": "Friday bookings", "client_ref": "bookings-1107" }'

Autenticación

Envía tu clave en el encabezado Authorization: Authorization: Bearer sso_live_.... Las claves tienen 49 caracteres y comienzan con sso_live_. Hasta 5 claves por cuenta; revoca una en el espacio de trabajo y dejará de funcionar de inmediato. La lista de precios es pública y no requiere clave.

Errores

Los errores usan códigos de estado HTTP y un cuerpo JSON con un código estable y una frase que puedes mostrar a una persona. No se cobra nada cuando falla una solicitud.

HTTP/1.1 402 Payment Required
{ "error": { "code": "insufficient_credit", "message": "Short by $3.20. Add funds to send.", "total": "8.62480", "credit": "5.42000" } }
CampoSignificado
400La solicitud necesita un cambio.
401Clave faltante, incorrecta o revocada.
402No hay suficiente saldo para el total.
404No existe tal envío, mensaje, país o endpoint.
409El envío ya ha comenzado.
429Más de 240 solicitudes en un minuto.

Límites

  • 240 solicitudes por minuto por clave, mostradas en X-RateLimit-Limit, X-RateLimit-Remaining y X-RateLimit-Reset (segundos unix).
  • 5,000 números por solicitud, 2,000 caracteres por texto, 10 segmentos por mensaje.
  • Los mensajes se envían a un máximo de 300 por minuto por cuenta.
  • send_at desde 10 minutos hasta 30 días adelante.

Precios

GET/api/v1/prices

GET/api/v1/prices/{country}

El precio por mensaje (un segmento) para cada país, en USD con 5 decimales. No se necesita clave.

{ "price": { "country": "GB", "name": "United Kingdom", "dial_code": "+44", "price_usd": "0.00868" }, "updated": "2026-11-07" }

Saldo de la cuenta

GET/api/v1/account

{ "account": "SO8K2M4Q7X9TBA", "credit_usd": "41.27660", "currency": "USD" }

Calcular el costo

POST/api/v1/costs

Toma el mismo cuerpo que un envío y devuelve el costo, sin enviar ni cobrar. El texto fusionado de cada destinatario decide sus segmentos, por lo que el total es exacto.

{
  "recipients": 2, "segments_min": 1, "segments_max": 1, "encoding": "gsm7", "messages_billed": 2,
  "total_usd": "0.01426", "credit_usd": "41.27660", "enough_credit": true,
  "countries": [ { "country": "GB", "recipients": 1, "price_usd": "0.00868", "subtotal_usd": "0.00868" },
                 { "country": "US", "recipients": 1, "price_usd": "0.00558", "subtotal_usd": "0.00558" } ],
  "dropped": [], "too_long": null
}

Crear un envío

POST/api/v1/sends

Un texto a uno o muchos números. El crédito para todo el envío se toma cuando se acepta; los mensajes fallidos se reembolsan uno por uno.

CampoSignificado
toNúmeros en formato internacional (+447700900461). Usa esto o destinatarios.
recipientsObjetos con to y fields, para campos de fusión: { "to": "+1...", "fields": { "first_name": "Ava" } }.
textEl mensaje, hasta 2,000 caracteres. {{field}} se reemplaza por destinatario; los valores faltantes se vuelven vacíos.
send_atHora ISO 8601 opcional con offset o Z. Sin ella, el envío comienza de inmediato.
nameEtiqueta opcional mostrada en el espacio de trabajo (80 caracteres).
client_refReferencia opcional devuelta en webhooks (64 caracteres).
opt_outAgrega la línea de exclusión guardada en Configuración al final del texto.
default_countryCódigo ISO utilizado para números escritos sin código de país.
HTTP/1.1 201 Created
{
  "send": { "id": "CA7Q29XMAB4DFK", "status": "queued", "source": "api", "recipients": 2, "segments_max": 1,
            "encoding": "gsm7", "cost_usd": "0.01426", "refunded_usd": "0.00000", "client_ref": "bookings-1107",
            "send_at": null, "created_at": "2026-11-07T10:12:03.120Z", "started_at": null, "finished_at": null },
  "dropped": [ { "value": "+4412", "code": "too_short", "message": "Too short" } ]
}

Los números que no se pueden enviar se enumeran en dropped y nunca se cobran. Códigos: no_country_code, too_short, too_long, not_a_number, wrong_length, not_covered, duplicate y suppressed (en tu lista de no enviar).

Obtener o listar envíos

GET/api/v1/sends/{id}

GET/api/v1/sends?limit=25&before={id}

Un solo envío incluye counts por estado de mensaje. Las listas son las más nuevas primero; pasa next_before como before para la siguiente página. Los envíos desde el espacio de trabajo también aparecen aquí, con source: "web".

Mensajes

GET/api/v1/sends/{id}/messages?status=failed&limit=100

GET/api/v1/messages/{id}

{ "message": { "id": "MS3D8RW0K2QJ7N", "send_id": "CA7Q29XMAB4DFK", "to": "+447700900461", "country": "GB",
  "segments": 1, "price_usd": "0.00868", "status": "delivered", "reason": null, "reason_text": null,
  "created_at": "2026-11-07T10:12:03.120Z", "sent_at": "2026-11-07T10:12:04.010Z", "done_at": "2026-11-07T10:12:09.400Z" } }

Cancelar un envío

POST/api/v1/sends/{id}/cancel

Solo los envíos programados que no han comenzado pueden cancelarse. El costo total regresa a tu saldo y la respuesta incluye refunded_usd.

Webhooks

Establece una URL https en el espacio de trabajo (API y webhooks). Enviamos un evento POST cuando un mensaje se entrega o falla, tanto para envíos desde la API como desde el espacio de trabajo. Responde con cualquier estado 2xx dentro de 10 segundos. Los envíos fallidos se intentan nuevamente hasta 7 veces en total: inmediatamente, luego después de 1 minuto, 5 minutos, 30 minutos, 1 hora, 2 horas y 3 horas. El espacio de trabajo muestra cada intento con su estado y respuesta.

POST https://your-server.example/sms-events
X-SSO-Event: message.delivered
X-SSO-Delivery: evt_5a0c9e31d2b7f4a68c
X-SSO-Timestamp: 1794132729
X-SSO-Signature: sha256=8f1c...e42a

{ "id": "evt_5a0c9e31d2b7f4a68c", "event": "message.delivered", "created": "2026-11-07T10:12:09.000Z",
  "data": { "message": { "id": "MS3D8RW0K2QJ7N", "send_id": "CA7Q29XMAB4DFK", "to": "+447700900461", "country": "GB",
  "segments": 1, "price_usd": "0.00868", "status": "delivered", "reason": null, "reason_text": null,
  "client_ref": "bookings-1107", "created_at": "...", "sent_at": "...", "done_at": "..." } } }

La firma es HMAC-SHA256 de timestamp + "." + raw body con tu secreto de firma (comienza con sso_whk_). Verifícalo en el cuerpo sin procesar antes de analizar, rechaza marcas de tiempo mayores a 5 minutos y usa el id de entrega para ignorar repeticiones.

import crypto from "node:crypto";

// rawBody: the request body exactly as received (a string), before JSON.parse
export function verify(rawBody, headers, secret) {
  const ts = headers["x-sso-timestamp"];
  const sig = headers["x-sso-signature"] || "";
  const want = "sha256=" + crypto.createHmac("sha256", secret).update(`${ts}.${rawBody}`).digest("hex");
  const fresh = Math.abs(Date.now() / 1000 - Number(ts)) < 300;
  return fresh && sig.length === want.length && crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(want));
}

Eventos: message.delivered, message.failed, y test desde el botón Enviar evento de prueba.

Estados y razones

CampoSignificado
queuedEsperando para salir de nuestra cola.
sentEntregado a la red, esperando el informe de entrega. La mayoría de los informes llegan 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.

Razones de fallo (el campo reason):

CampoSignificado
network_errorError de red
absentTeléfono apagado o fuera de 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: ningún informe antes del límite de tiempo de la red
canceledCancelado antes de enviar
unknownRespuesta de red desconocida

Los controles de servicio están en la página de estado y los cambios de API en el registro de cambios.

Obtén tu clave API

Crea una cuenta, añade fondos una vez y abre API y webhooks en el espacio de trabajo. La misma clave funciona para todos los países.

Crear cuenta gratuita
Documentación de la API de SMS: Envía SMS masivos desde tu código | SendSMSOnline