Clave para «Probar»:ninguna — los ejemplos no se envíanRegístrese para obtener una clave de prueba
La clave se queda en esta pestaña: no se guarda y solo se envía a https://api.envoisms.ma.

Inicio rápido

Su primera llamada en un minuto#

La API de EnvoiSMS envía SMS y mensajes de WhatsApp y verifica números de teléfono, en JSON sobre HTTPS. Cree una clave sandbox, pegue un ejemplo y ejecútelo: no se envía ni se cobra nada.

Para agentes de IA de código: llms.txt · MCP
URL basehttps://api.envoisms.ma/v1
AutenticaciónAuthorization: Bearer <clave>
Clavessmr_… (live) · env_test_… (sandbox: nada enviado, nada cobrado)
FormatoJSON de entrada y salida, UTF-8, fechas ISO 8601 (UTC)
NúmerosE.164: +212612345678
LímitePor clave y minuto, según el plan (60 a 1 200); ver X-RateLimit-*
  1. Cree una cuenta y luego una clave sandbox en Panel → Claves API, y expórtela: export ENVOISMS_API_KEY=env_test_…
  2. Pegue uno de los ejemplos de al lado: funciona tal cual con esa clave, sin enviar ni cobrar nada.
  3. Registre un webhook para recibir los estados de entrega y las respuestas.
  4. Recargue, cree una clave live (smr_…) y sustitúyala por la clave sandbox en su configuración.

1. Enviar un SMS

Una sola llamada. Con una clave sandbox (env_test_…) el envío se simula: nada sale de la plataforma, no se cobra nada y la respuesta indica el precio que pagaría una clave live (simulated_cost).

Respuesta
HTTP/1.1 202 Accepted
{
  "id": "msg_9f2c4e1a7b3d",
  "to": "+212612345678",
  "channel": "sms",
  "cascade": false,
  "status": "queued",
  "cost": { "eur": 0, "mad": 0 },
  "sandbox": true,
  "simulated_cost": { "eur": 0.0704, "mad": 0.792 },
  "segments": 1,
  "created_at": "2026-10-22T10:30:00.000Z"
}
1. Enviar un SMS
curl https://api.envoisms.ma/v1/messages \
  -H "Authorization: Bearer $ENVOISMS_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "to": "+212612345678",
    "message": "Your order #1042 has shipped."
  }'

2. Enviar una plantilla de WhatsApp

WhatsApp envía desde su propio número de WhatsApp Business, conectado una vez en el panel (si no, 403 WHATSAPP_NOT_CONNECTED). Fuera de la ventana de 24 h solo una plantilla aprobada puede abrir la conversación; sus variables {{1}}, {{2}}… van en metadata.variables, en orden.

Respuesta
HTTP/1.1 202 Accepted
{
  "id": "msg_4b81d0c2e9f7",
  "to": "+212612345678",
  "channel": "whatsapp",
  "cascade": false,
  "status": "queued",
  "cost": { "eur": 0.05778, "mad": 0.65 },
  "segments": 1,
  "created_at": "2026-10-22T10:31:00.000Z"
}
2. Enviar una plantilla de WhatsApp
curl https://api.envoisms.ma/v1/messages \
  -H "Authorization: Bearer $ENVOISMS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "+212612345678",
    "channel": "whatsapp",
    "template": { "name": "order_shipped", "language": "fr" },
    "metadata": { "variables": ["Karim", "#1042"] }
  }'

3. Verificar un número (OTP)

EnvoiSMS genera el código, lo entrega, cuenta los intentos y lo hace caducar (10 min por defecto). Usted solo guarda session_id. Con una clave sandbox la respuesta incluye sandbox_code para probar /verify/check sin teléfono.

Respuesta
POST /v1/verify/send → 202 Accepted
{
  "session_id": "vrf_2c7e9a14b0d3",
  "message_id": "msg_e1a07c93b5f2",
  "to": "+212612345678",
  "channel": "sms",
  "expires_at": "2026-10-22T10:42:00.000Z",
  "resend_after_seconds": 30,
  "fallback_channels": [],
  "status": "sent",
  "sandbox": true,
  "simulated": true,
  "sandbox_code": "483921"
}

POST /v1/verify/check → 200 OK
{ "session_id": "vrf_2c7e9a14b0d3", "verified": true, "verified_at": "2026-10-22T10:33:12.000Z" }
3. Verificar un número (OTP)
# 1. Send the code
curl https://api.envoisms.ma/v1/verify/send \
  -H "Authorization: Bearer $ENVOISMS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "to": "+212612345678", "brand": "MyApp" }'

# 2. Check what the user typed
curl https://api.envoisms.ma/v1/verify/check \
  -H "Authorization: Bearer $ENVOISMS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "session_id": "vrf_…", "code": "483921" }'

Autenticación

Autenticación#

Cada petición /v1 lleva una clave API en la cabecera Authorization. Cree, limite y revoque sus claves en Panel → Claves API; una clave se muestra una sola vez, al crearla. Úsela solo en el servidor.

Dos tipos de clave

smr_… envía de verdad y descuenta del saldo; env_test_… lo simula todo, gratis. Misma URL para ambas.

Permisos

Cada clave lleva una lista de permisos (send, verify, status…). Fuera de esa lista: 403 FORBIDDEN.

Lista de IP permitidas

Opcional, por clave. Una llamada desde otra IP recibe 401 UNAUTHORIZED.

Cabeceras de límite

X-RateLimit-Limit, X-RateLimit-Remaining y X-RateLimit-Reset (tiempo Unix) en cada respuesta.

Cabeceras de la petición
POST /v1/messages HTTP/1.1
Host: api.envoisms.ma
Authorization: Bearer smr_your_live_key
Content-Type: application/json
Idempotency-Key: 6f1d2c9e-4b7a-4f3e-9d21-0c5e8a7b3f10

HTTP/1.1 202 Accepted
X-RateLimit-Limit: 180
X-RateLimit-Remaining: 179
X-RateLimit-Reset: 1792663860

Confirmación de pedido (SMS)

Enviar una confirmación de pedido por SMS#

Actualizado el

  1. Cree una clave sandbox y expórtela: export ENVOISMS_API_KEY=env_test_…
  2. Opcional: solicite su nombre de remitente (POST /v1/sender-ids, 15 a 30 días). Mientras tanto, omita sender_id: se aplica el remitente por defecto.
  3. En su evento «pedido pagado», envíe el SMS con Idempotency-Key = el id del pedido: un disparo doble envía un solo mensaje.
  4. Registre un webhook para message.delivered y message.failed; metadata.order_id vuelve en cada entrega.
  5. Pruebe +212600000002 para ver la rama de fallo y luego cambie a una clave live (smr_…).
¿Le ha sido útil esta sección?
Confirmación de pedido (SMS)
curl https://api.envoisms.ma/v1/messages \
  -H "Authorization: Bearer $ENVOISMS_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-1042-paid" \
  -d '{
    "to": "+212612345678",
    "message": "Commande #1042 confirmée : 349 MAD. Livraison prévue jeudi.",
    "metadata": { "order_id": "1042" }
  }'

Pedidos COD en WhatsApp

Confirmar pedidos contra reembolso por WhatsApp#

Actualizado el

En WooCommerce, el plugin de EnvoiSMS (v2.0.0) lo hace todo: cada pedido COD espera, el cliente recibe un mensaje con los botones Confirmar / Cancelar, y su toque actualiza el pedido.

  1. Conecte su número de WhatsApp en Panel → WhatsApp (puede seguir usando la app WhatsApp Business en su teléfono).
  2. Instale el plugin desde github.com/envoisms/envoisms-woocommerce (WooCommerce 7.1+, PHP 8.0+) y pegue su clave API en WooCommerce → EnvoiSMS Maroc.
  3. Marque COD «Activer» y guarde: el plugin registra su webhook (message.inbound) y guarda el secreto de firma.
  4. Pulse «Créer les modèles WhatsApp» (francés y árabe): EnvoiSMS las revisa y las envía a Meta; su estado aparece en la misma página.
  5. Cuando las plantillas estén aprobadas, haga un pedido de prueba contra reembolso. Puede seguir su estado por la API:
¿Le ha sido útil esta sección?
Pedidos COD en WhatsApp
curl "https://api.envoisms.ma/v1/templates?channel=whatsapp" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY"

Campaña de marketing (API)

Lanzar una campaña de marketing por la API#

Actualizado el

  1. Cree una lista: POST /v1/contacts/lists {"name": "Clientes fieles"} → lst_…
  2. Importe los contactos: POST /v1/contacts/import {"list_id", "contacts": [{"phone", "name"}]}.
  3. Registre la prueba del consentimiento de cada uno: POST /v1/consents {"phone", "purpose": "marketing", "status": "granted", "source": "form", "evidence": {…}}.
  4. Cree la campaña: SMS con body (indique cómo darse de baja); WhatsApp con template_id, template_variables y "consent_attested": true. scheduled_at la programa.
  5. Lánzela (POST /v1/campaigns/{id}/send), sígala con GET /v1/campaigns/{id}, páusela con /pause. Los números dados de baja (STOP) se excluyen automáticamente.
¿Le ha sido útil esta sección?
Campaña de marketing (API)
curl https://api.envoisms.ma/v1/campaigns \
  -H "Authorization: Bearer $ENVOISMS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Soldes de novembre",
    "channel": "sms",
    "list_id": "lst_…",
    "body": "-20% ce week-end avec le code NOV20.",
    "scheduled_at": "2026-11-07T09:00:00Z"
  }'

Bandeja bidireccional (webhooks)

Una bandeja de soporte bidireccional#

Actualizado el

  1. Registre un webhook para message.inbound (y message.read si muestra confirmaciones de lectura).
  2. Verifique la firma y archive el mensaje por número: data.from es el cliente, data.channel el canal.
  3. Lea el historial de un cliente con GET /v1/conversations/{phone}.
  4. Responda con POST /v1/conversations/{phone}/messages. En WhatsApp, el texto libre solo es posible en las 24 h siguientes al último mensaje del cliente; después, envíe una plantilla (template_name) o pase a SMS.
  5. Marque el hilo como leído con POST /v1/conversations/{phone}/mark-read. Un STOP del cliente llega como contact.optout y bloquea los envíos siguientes.
¿Le ha sido útil esta sección?
Bandeja bidireccional (webhooks)
curl https://api.envoisms.ma/v1/conversations/+212612345678/messages \
  -H "Authorization: Bearer $ENVOISMS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "message": "Bonjour, votre colis part aujourd'"'"'hui." }'

Plataformas y agencias

Conectar el WhatsApp de sus clientes#

Actualizado el

Un número de WhatsApp no se conecta por la API: Meta exige que el propietario del negocio inicie sesión él mismo (Embedded Signup). Por eso cada cliente tiene su propia cuenta de EnvoiSMS, que paga sus envíos.

  1. El cliente crea su cuenta en envoisms.ma (registro por e-mail).
  2. Conecta su número en Panel → WhatsApp iniciando sesión en Meta (Embedded Signup; coexistencia con la app WhatsApp Business posible).
  3. Crea una clave API limitada a lo que necesita (p. ej. send, status) y se la entrega; usted la guarda en el servidor, una por cliente.
  4. Compruebe que el número puede enviar: GET /v1/whatsapp/waba-connection → readiness.can_send y readiness.blockers.
  5. Cree sus plantillas (POST /v1/templates) y envíe con su clave. Sus envíos, su saldo y sus webhooks siguen siendo suyos.
¿Le ha sido útil esta sección?
Plataformas y agencias
curl https://api.envoisms.ma/v1/whatsapp/waba-connection \
  -H "Authorization: Bearer $CLIENT_API_KEY"

Sandbox y pruebas

Probar sin enviar ni pagar#

Actualizado el

Cree una clave sandbox en Panel → Claves API (marque «Sandbox»). Empieza por env_test_; una clave live empieza por smr_. Ambas llaman a la misma URL, https://api.envoisms.ma/v1: decide la clave, no la dirección.

Una clave sandbox se valida igual que una live (formato del número, Sender ID, ventana de 24 h, contenido), pero no se envía ni se cobra nada. Lo que una clave live habría rechazado se indica en warnings en lugar de ocultarse.

  • POST /v1/messages devuelve sandbox: true, cost a cero y simulated_cost: el precio real del mismo envío.
  • POST /v1/verify/send devuelve sandbox_code: el código que habría recibido el usuario.
  • Cada clave solo ve su propio tráfico: GET /v1/messages con una clave sandbox solo lista los envíos simulados, y a la inversa.
  • Números de prueba (POST /v1/messages, solo clave sandbox): +212600000002 falla (message.failed, SANDBOX_TEST_FAILED); +212600000003 no es entregable (message.undeliverable); +212600000004 está dado de baja (403 OPTED_OUT en la petición); +212600000005 nunca recibe acuse (queda «sent»); cualquier otro número, incluido +212600000001, se entrega (message.delivered) unos 2 s después del envío.
  • Una clave sandbox no puede convertirse en live: cree una clave live cuando esté listo y cambie la variable de entorno.
¿Le ha sido útil esta sección?

¿SMS o WhatsApp?

Elegir el canal#

Actualizado el

SMS es el canal por defecto: omita channel. Llega a cualquier teléfono, sin número conectado, sin plantilla y sin ventana de 24 h. Es la opción correcta para una notificación o alerta a cualquier cliente.

WhatsApp ("channel": "whatsapp") requiere su propio número de WhatsApp Business conectado. Para escribir primero, o más de 24 h después del último mensaje del cliente, use una plantilla aprobada; el texto libre solo se acepta en las 24 h siguientes a un mensaje del cliente (si no, 400 OUT_OF_24H_WINDOW, nada cobrado). WhatsApp se factura según la categoría de la plantilla.

Ambos a la vez: "cascade": true prueba WhatsApp y pasa a SMS si WhatsApp rechaza, falla o no confirma la entrega en cascade_timeout segundos (120 por defecto). Solo se factura lo que realmente salió.

Códigos de verificación: prefiera POST /v1/verify/send, que gestiona generación, intentos, caducidad y reenvío, en lugar de enviar su propio código por /v1/messages.

¿Le ha sido útil esta sección?

Idempotencia y reintentos

Reintentar sin enviar dos veces#

Actualizado el

Añada una cabecera Idempotency-Key (hasta 255 caracteres, p. ej. un UUID o «pedido-1042-enviado») a POST /v1/messages y POST /v1/messages/bulk (y sus equivalentes /v1/waba/messages). Si la llamada expira o su proceso falla, repita la misma petición con la misma clave: el envío ocurre una sola vez y el reintento devuelve el cuerpo JSON original con Idempotency-Replayed: true.

  • La clave se conserva 24 h, por cuenta. Toda respuesta inferior a 500 se guarda y se repite, errores 4xx incluidos (p. ej. 429 o 402): tras una 4xx, corrija la causa y use una clave NUEVA.
  • La clave está ligada al cuerpo exacto, byte a byte: un reintento reenvía el mismo cuerpo. Otro cuerpo con la misma clave — incluso el mismo JSON con otro formato — devuelve 422 IDEMPOTENCY_KEY_REUSED.
  • Un 5xx o un tiempo agotado no se guarda: reintente con la MISMA clave, con espera creciente (1 s, 2 s, 4 s…).
  • 409 IDEMPOTENCY_IN_FLIGHT: la petición original sigue en curso; espere Retry-After y reintente con la misma clave. 422 IDEMPOTENCY_KEY_REUSED: la clave ya se usó con otro cuerpo.
  • 429 RATE_LIMITED: espere los segundos indicados en Retry-After. X-RateLimit-Limit, X-RateLimit-Remaining y X-RateLimit-Reset acompañan a cada respuesta autenticada.
¿Le ha sido útil esta sección?

Paginación

Recorrer una lista#

Actualizado el

Las listas aceptan limit y offset y devuelven { "data": [...], "limit", "offset", "total" }. Para GET /v1/messages: 50 por defecto, 200 como máximo, del más reciente al más antiguo, filtrable por status, channel, campaign_id, from_date y to_date. Añada format=csv para obtener todo el conjunto filtrado en un archivo.

¿Le ha sido útil esta sección?
Paginación
curl "https://api.envoisms.ma/v1/messages?status=failed&limit=100&offset=0" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY"

Permisos de claves

Una clave, el mínimo acceso#

Actualizado el

Cada clave lleva permisos. Una llamada fuera de ellos devuelve 403 FORBIDDEN. Dé a cada servidor la lista más pequeña que funcione: un backend de OTP solo necesita verify y status.

  • send — POST /v1/messages, /v1/messages/bulk, /v1/templates, /v1/conversations, /v1/lookup
  • status — GET /v1/messages, POST /v1/verify/check y el resto de /v1/verify (sessions, apps, lookup, void)
  • verify — POST /v1/verify/send, /v1/verify/resend
  • balance — GET /v1/billing/balance; billing — el resto de /v1/billing
  • contacts (contactos, listas, consentimientos), campaigns, webhooks, optouts, analytics, api_keys, sender_id
  • Configuración de WhatsApp (/v1/whatsapp/…): los cambios — conectar un número, herramientas, perfil, relés — exigen una sesión del panel (una clave API recibe 403). Solo la lectura está abierta a las claves, p. ej. GET /v1/whatsapp/waba-connection.
¿Le ha sido útil esta sección?

Versiones y cambios

Qué puede cambiar y cómo lo sabrá#

Actualizado el

La versión está en la ruta: /v1. Dentro de /v1 la API solo cambia por adición: nuevos endpoints, campos opcionales, campos de respuesta, códigos de error, eventos de webhook. Ignore los campos que no conozca. Un cambio incompatible sale como /v2 junto a /v1, nunca en su lugar.

Retirar algo de /v1 se anuncia con al menos 12 meses de antelación: por e-mail al titular de la cuenta, en el registro de cambios y en la propia ruta, cuyas respuestas llevan entonces Deprecation (fecha del anuncio), Sunset (fecha de fin) y Link a la guía de migración. La única excepción es una corrección de seguridad, anunciada lo antes posible. Ninguna ruta de /v1 está obsoleta hoy.

¿Le ha sido útil esta sección?

Límites por endpoint

Límites, endpoint por endpoint#

Actualizado el

Todas las rutas comparten el límite por minuto de la clave (por defecto Starter 60, Business 180, Pro 500, Enterprise 1 200; X-RateLimit-* en cada respuesta; 429 RATE_LIMITED con Retry-After por encima). Algunas rutas añaden reglas propias:

  • POST /v1/messages/bulk: 10 000 mensajes por llamada.
  • POST /v1/messages y /bulk: cuotas diaria y mensual opcionales de la cuenta (PATCH /v1/billing/quota); por encima, 429 QUOTA_EXCEEDED hasta medianoche UTC.
  • POST /v1/verify/send y /resend: por número, un código cada 30 s por SMS y 60 s por WhatsApp (429 OTP_COOLDOWN), 5 por hora y 10 por día (429 OTP_RATE_LIMIT); 200 por día y cuenta; fuera de Marruecos, OTP_COUNTRY_PAUSED cuando se confirman muy pocos.
  • POST /v1/verify/check: 3 intentos por sesión por defecto (configurables por app Verify), luego 429 MAX_ATTEMPTS.
  • POST /v1/inbound-webhooks/catch/{id} (sin clave): 120 peticiones por minuto por webhook, 64 KB por petición.
  • Sus webhooks: esperamos su respuesta 10 s como máximo y reintentamos 3 veces (1 min, 10 min, 1 h).
¿Le ha sido útil esta sección?

Webhooks

Recibir estados y respuestas#

Actualizado el

Cree un webhook con POST /v1/webhooks ({"url": "https://…", "events": ["message.delivered", "message.failed"]}). La respuesta incluye el secreto de firma, mostrado una sola vez; POST /v1/webhooks/{id}/rotate-secret emite uno nuevo. events acepta un nombre exacto, un espacio de nombres ("message.*") o "*".

Cada entrega es un POST JSON {"event", "data", "timestamp"}. Para un estado de mensaje, data es {id, to, channel, status, error_code?, error_message?, metadata?} — metadata es la que usted envió. Responda 2xx antes del timeout de 10 segundos y procese de forma asíncrona; deduplique por data.id + event, ya que una entrega puede llegar dos veces.

Verifique X-EnvoiSMS-Signature-256: t=<segundos>,v1=<hex>: calcule HMAC-SHA256(secreto, t + "." + cuerpo bruto), compárelo con v1 en tiempo constante y rechace si t tiene más de 5 minutos. La cabecera antigua X-EnvoiSMS-Signature (sha256=<hex> del cuerpo solo, sin marca de tiempo) aún se envía; prefiera la nueva, que impide la repetición. El evento también va en X-EnvoiSMS-Event.

  • Reintentos: un 5xx, un 429 o un error de red se reintenta 3 veces (tras 1 min, 10 min y 1 h: 4 intentos en total). Otro 4xx no se reintenta.
  • Registro y repetición: GET /v1/webhooks/logs lista los últimos 100 intentos; POST /v1/webhooks/logs/{log_id}/replay reenvía una entrega, firmada de nuevo. POST /v1/webhooks/{id}/test envía un ejemplo.
  • Relé de WhatsApp a su servidor (número conectado en modo «webhook» en el panel): los mensajes y estados llegan en el formato nativo de WhatsApp Cloud, firmados con X-Hub-Signature-256: sha256=<hex> = HMAC-SHA256(webhook_secret del número, cuerpo bruto). Ese secreto se muestra una vez, al crearse o renovarse (rotate_webhook_secret). Sin secreto no se reenvía nada. No hay marca de tiempo: deduplique por el id del mensaje.

Eventos

message.sent

El mensaje se entregó a la red (operador o WhatsApp).

message.delivered

El teléfono del destinatario confirmó la recepción.

message.read

WhatsApp: el destinatario leyó el mensaje (salvo que haya desactivado las confirmaciones de lectura).

message.failed

El envío falló; error_code y error_message indican el motivo (ver Códigos de fallo de entrega).

message.undeliverable

La red confirmó que el mensaje no puede entregarse (número inexistente, caducado en el operador).

message.fallback

Cascada: el mensaje pasa al siguiente canal. channel = canal nuevo, previous_channel = el anterior, error_code = el motivo.

message.inbound

Un contacto le escribió (respuesta SMS o mensaje de WhatsApp, incluido un toque en botón o lista).

message.reaction

WhatsApp: un contacto reaccionó a uno de sus mensajes con un emoji.

contact.optout / contact.optin

Un contacto se dio de baja (STOP) o volvió a suscribirse. Los envíos a un número dado de baja se rechazan (403 OPTED_OUT).

lead.qualified

El bot de calificación de WhatsApp terminó de calificar un cliente potencial.

¿Le ha sido útil esta sección?
Verificar la firma
import crypto from 'node:crypto';
import express from 'express';

const app = express();

// Keep the RAW body: the signature is computed over the exact bytes received.
app.post('/webhooks/envoisms', express.raw({ type: 'application/json' }), (req, res) => {
  const header = req.get('X-EnvoiSMS-Signature-256') || '';
  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
  const expected = crypto
    .createHmac('sha256', process.env.ENVOISMS_WEBHOOK_SECRET)
    .update(`${parts.t}.${req.body}`)
    .digest('hex');

  const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
  const valid = parts.v1?.length === expected.length
    && crypto.timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected));
  if (!fresh || !valid) return res.sendStatus(401);

  const { event, data } = JSON.parse(req.body);
  res.sendStatus(200);              // answer fast…
  queue.push({ event, data });      // …then process (de-duplicate on data.id + event)
});
Ejemplo de entrega
POST /webhooks/envoisms HTTP/1.1
Content-Type: application/json
X-EnvoiSMS-Event: message.delivered
X-EnvoiSMS-Signature-256: t=1792663800,v1=5f0c…e9
X-EnvoiSMS-Signature: sha256=a41b…7d
User-Agent: EnvoiSMS-Webhook/1.0 (formerly SMSMaroc)

{
  "event": "message.delivered",
  "data": {
    "id": "msg_9f2c4e1a7b3d",
    "to": "+212612345678",
    "channel": "sms",
    "status": "delivered",
    "metadata": { "order_id": "1042" }
  },
  "timestamp": "2026-10-22T10:30:04.000Z"
}

Errores

Errores#

Un error devuelve un estado 4xx o 5xx y siempre el mismo sobre. Bifurque por error.code, nunca por el texto: error.message es una frase en inglés cuya redacción puede mejorar. Cada código indica qué hacer.

HTTP/1.1 400 Bad Request
{
  "error": {
    "code": "INVALID_PHONE",
    "message": "to must be E.164 format, for example +212612345678",
    "docs": "https://envoisms.ma/en/docs#errors"
  }
}

Autenticación y acceso

UNAUTHORIZEDHTTP 401

Falta la cabecera Authorization, la clave es desconocida o está revocada, la IP no está en la lista permitida de la clave, o la cuenta no está activa; error.message indica cuál. Envíe Authorization: Bearer <clave> y compruébela en Panel → Claves API.

FORBIDDENHTTP 403

La clave no tiene permiso para esta ruta (ver Permisos de claves), o la ruta exige una sesión del panel. Añada el permiso a la clave o use otra.

ACCOUNT_SUSPENDEDHTTP 403

La cuenta está suspendida. Contacte con soporte; no se puede enviar nada hasta su revisión.

WORKSPACE_NOT_FOUNDWORKSPACE_SUSPENDEDAPI_KEY_WORKSPACE_MISMATCHHTTP 404 / 403

La organización especificada en la cabecera x-workspace-id no existe, está suspendida o entra en conflicto con la clave API.

FORBIDDEN_ASSETHTTP 403

Ese número de WhatsApp no pertenece a su cuenta. Use un phone_number_id de sus propios números conectados.

PAYMENT_NOT_SETTLEDINVALID_CREDENTIALMACHINE_PAYMENTS_DISABLEDHTTP 402 / 400 / 501

Pagos de máquina (Authorization: Payment …): el depósito aún no se ha confirmado (espere y reintente), la credencial no se decodifica o la función está desactivada.

Petición mal formada

INVALID_JSONHTTP 400

El cuerpo no es JSON válido. Envíe Content-Type: application/json y un objeto JSON.

MISSING_FIELDMISSING_FIELDSINVALID_FIELDMISSING_BODYMISSING_MESSAGEMISSING_URLHTTP 400

Falta un campo obligatorio o su valor está fuera de rango; error.message nombra el campo. Corríjalo y reenvíe.

INVALID_PHONEHTTP 400

El número no está en formato E.164. Envíe + prefijo y el número, sin espacios: +212612345678.

INVALID_URLHTTP 400

Una URL no es https:// o no es válida (webhook, medio, botón). Use una URL https pública.

INVALID_CHANNELCHANNEL_NOT_CONFIGUREDCHANNEL_DISABLEDUNSUPPORTED_CHANNELHTTP 400

Canal desconocido o no disponible. En /v1/messages, channel es sms (por defecto) o whatsapp; en /v1/consents, whatsapp, sms o any.

INVALID_PURPOSEINVALID_STATUSINVALID_SOURCEINVALID_EVIDENCEINVALID_DATEHTTP 400

Consentimientos: purpose = marketing | transactional | otp | service; status = granted | withdrawn; source = api | form | import | dashboard; evidence = objeto JSON < 4 KB; fechas ISO 8601, recorded_at nunca en el futuro.

INVALID_PERMISSIONSSANDBOX_IMMUTABLEHTTP 400

Claves API: indique al menos un permiso válido; una clave nunca cambia entre sandbox y live — cree una nueva.

NOT_FOUNDLIST_NOT_FOUNDLEAD_NOT_FOUNDTEMPLATE_NOT_FOUNDAPP_NOT_FOUNDSHEET_NOT_FOUNDINVALID_MESSAGE_IDHTTP 400 / 404

El recurso no existe para esta cuenta (o este modo de clave: una clave sandbox no ve objetos live), o el identificador solicitado no es válido (ej. null o indefinido). Compruebe el id y la clave.

METHOD_NOT_ALLOWEDHTTP 405

Ese método HTTP no existe en esta ruta. Consulte la referencia del endpoint.

UNSUPPORTED_MEDIA_TYPEPAYLOAD_TOO_LARGEFILE_TOO_LARGEINVALID_FILE_TYPEINVALID_FORM_DATAMISSING_FILEEMPTY_SHEETPARSE_FAILEDMAPPING_ERRORFETCH_FAILEDINVALID_PATHNO_FILE_PROVIDEDNO_DATA_PROVIDEDUPLOAD_FAILEDHTTP 400 / 413 / 415 / 502

Archivo o importación rechazados: tipo no admitido, demasiado grande, vacío, columnas no reconocidas o URL inaccesible. error.message indica cuál; corrija el archivo y reenvíe.

Envío (SMS y común)

INSUFFICIENT_BALANCEHTTP 402

Saldo insuficiente para este envío. Recargue en Panel → Facturación; no se cobró nada.

INTERNATIONAL_REQUIRES_TOPUPHTTP 403

Los envíos fuera de Marruecos son para cuentas que ya han recargado. Haga una primera recarga.

QUOTA_EXCEEDEDHTTP 429

Se alcanzó la cuota diaria fijada en su cuenta; se reinicia a medianoche UTC. Auméntela en el panel si es necesario. Tras este 429, reintente con una nueva Idempotency-Key.

OPTED_OUTHTTP 403

Este destinatario se dio de baja de sus mensajes (STOP). No reenvíe; quítelo de sus listas. Puede volver a suscribirse él mismo.

SENDER_ID_TOO_LONGSENDER_ID_TOO_SHORTSENDER_ID_INVALIDHTTP 400

Un Sender ID alfanumérico tiene de 3 a 11 caracteres (letras, cifras, espacio). Corríjalo u omítalo para usar el remitente por defecto.

SENDER_ID_GENERICHTTP 400

Sender ID genérico (INFO, ALERT, SERVICE…), no una marca. Use su nombre de marca u omítalo.

SENDER_ID_PROTECTED_BRANDHTTP 403

El Sender ID nombra o imita a una institución protegida (banco, operador, administración). Reservado a la cuenta aprobada para ese nombre exacto.

SENDER_ID_NOT_APPROVEDSENDER_ID_PENDINGSENDER_ID_IN_REVIEWSENDER_ID_REJECTEDSENDER_ID_APPROVEDHTTP 403 / 409

Registro de Sender ID: aún no aprobado, en revisión, rechazado o ya aprobado (409). Siga su estado en Ajustes → Sender IDs.

SENDER_ID_NOT_REGISTEREDHTTP 403

Ya no se devuelve desde el 27/09/2026: un Sender ID no registrado se envía automáticamente con la tarifa estándar. Se mantiene para que el código que lo comprueba siga siendo válido.

SENDER_NOT_ASSIGNED_TO_WORKSPACEHTTP 403

El Sender ID especificado no está asignado a este espacio de trabajo. Asígnelo en Ajustes → Espacios de trabajo.

NO_SENDER_ID_ASSIGNEDHTTP 422

No hay ningún Sender ID asignado a este espacio de trabajo. Asigne un Sender ID aprobado en Ajustes → Espacios de trabajo.

WORKSPACE_BALANCE_EXHAUSTEDHTTP 402

El saldo asignado a este espacio de trabajo se ha agotado. Asigne más saldo desde la cuenta principal en Ajustes → Espacios de trabajo.

SPAM_OR_PHISHING_DETECTEDCONTENT_BLOCKEDHTTP 400

Contenido rechazado: enlace de phishing, mensaje en nombre de un banco, operador o administración, o violación del uso aceptable. La cuenta queda suspendida pendiente de revisión. Recurso: POST /v1/moderation/appeals con error.appeal.id, error.appeal.token y una nota (sin clave API).

CONTENT_REFUSEDHTTP 400

Contenido rechazado por el control anti-abuso (primer rechazo). No se envió nada y la cuenta sigue activa; un segundo rechazo la suspende. Reformule o recurra vía POST /v1/moderation/appeals.

INVALID_APPEALAPPEAL_NOT_FOUNDAPPEAL_ALREADY_FILEDHTTP 400 / 404 / 409

Recurso: id o token ausente o erróneo, o ya hay un recurso abierto para este rechazo.

EMPTY_BATCHTOO_MANY_MESSAGESHTTP 400

POST /v1/messages/bulk acepta de 1 a 10 000 mensajes por llamada. Divida los lotes mayores.

SEND_PERSIST_FAILEDHTTP 502

No se pudo registrar el mensaje; no se envió ni se cobró nada. Reintente con la misma Idempotency-Key.

WhatsApp

WHATSAPP_NOT_CONNECTEDHTTP 403

No hay ningún número de WhatsApp Business conectado. Conéctelo en el panel; para un texto simple, omita channel (SMS). No se cobró nada.

OUT_OF_24H_WINDOWHTTP 400

Mensaje libre de WhatsApp a un contacto que no le escribió en 24 h. Envíe una plantilla aprobada (template) o un SMS. No se cobró nada.

CONTACT_HAS_NO_PHONEHTTP 400

Este contacto usa un nombre de usuario de WhatsApp y no ha compartido su número: solo se le puede contactar por WhatsApp, no por SMS. No se cobró nada.

WHATSAPP_ONLY_FIELDBUTTONS_NOT_SUPPORTEDINTERACTIVE_NOT_SUPPORTEDHTTP 400

Se envió por SMS un campo exclusivo de WhatsApp (buttons, interactive, reaction, contacts, location, sticker, context). Añada "channel": "whatsapp" o quite el campo.

CONFLICTING_MESSAGE_TYPESCASCADE_NOT_SUPPORTEDINVALID_MESSAGEHTTP 400

Un mensaje lleva un solo tipo de contenido; reaction, contacts, location y sticker no se combinan con nada y no tienen equivalente SMS (sin cascade).

INVALID_REACTIONINVALID_CONTEXTINVALID_CONTACTSINVALID_LOCATIONINVALID_MEDIAHTTP 400

Objeto de WhatsApp mal formado: reaction = {message_id, emoji}; context = {message_id}; contacts = 1–20 fichas con name.formatted_name; location = latitude/longitude numéricas; medio = "id" O "link" https, sin pie en audio/sticker.

INVALID_INTERACTIVEINVALID_INTERACTIVE_TYPEMISSING_INTERACTIVE_ACTIONINTERACTIVE_BODY_TOO_LONGINVALID_CTA_URL_ACTIONMISSING_CTA_DISPLAY_TEXTCTA_DISPLAY_TEXT_TOO_LONGINVALID_LOCATION_REQUEST_ACTIONINVALID_FLOW_ACTIONMISSING_FLOW_IDMISSING_FLOW_CTAFLOW_CTA_TOO_LONGMISSING_CAROUSEL_CARDSTOO_MANY_CAROUSEL_CARDSINVALID_PRODUCT_ACTIONINVALID_PRODUCT_LISTMISSING_ORDER_ACTIONMISSING_ORDER_CURRENCYMISSING_ORDER_ITEMSMISSING_ORDER_PARAMETERSMISSING_REFERENCE_IDINVALID_TOTAL_AMOUNTMISSING_PHONE_NUMBERHTTP 400

Mensaje interactive mal formado (lista, botones, cta_url, flow, carrusel, producto, pedido, location_request). error.message nombra el campo erróneo y el límite de WhatsApp superado.

MISSING_BUTTONS_ARRAYINVALID_BUTTONINVALID_BUTTON_COUNTINVALID_BUTTON_TEXTINVALID_BUTTON_TYPEINVALID_REPLY_BUTTONBUTTON_TEXT_TOO_LONGBUTTON_TITLE_TOO_LONGTOO_MANY_BUTTONSMISSING_COPY_CODEMISSING_LIST_BUTTONLIST_BUTTON_TOO_LONGMISSING_LIST_SECTIONSTOO_MANY_SECTIONSSECTION_TITLE_TOO_LONGINVALID_SECTION_ROWSTOO_MANY_ROWSMISSING_ROW_IDMISSING_ROW_TITLEROW_ID_TOO_LONGROW_TITLE_TOO_LONGROW_DESC_TOO_LONGHTTP 400

Botones o lista fuera de los límites de WhatsApp: máx. 3 botones, títulos de botón de 20 caracteres, 10 secciones y 10 filas, títulos de fila de 24 caracteres, descripción de 72. error.message da el límite exacto.

META_SERVICE_UNAVAILABLEHTTP 502

WhatsApp no estuvo disponible momentáneamente y el envío falló tras los reintentos automáticos. Reintente en unos minutos.

Plantillas y campañas

TEMPLATE_PENDING_REVIEWTEMPLATE_REJECTED_BY_REVIEWHTTP 409

La plantilla espera la revisión de EnvoiSMS (pasa a Meta una vez aprobada), o esa revisión la rechazó: edítela para volver a enviarla a revisión.

TEMPLATE_EXISTSTEMPLATE_ALREADY_ON_METATEMPLATE_EDIT_NOT_ALLOWEDTEMPLATE_IMMUTABLE_FIELDHTTP 400 / 409

Ya existe una plantilla con ese nombre, ya está en Meta (sincronícela), no se puede editar en su estado actual, o cambia un campo que Meta fija (nombre, idioma, categoría de una plantilla aprobada) — cree una nueva.

INVALID_LANGUAGEINVALID_META_TEMPLATEINVALID_AUTH_TEMPLATEMISSING_SAMPLE_VALUESMISSING_HEADER_EXAMPLEINVALID_HEADER_EXAMPLEHEADER_EXAMPLE_INVALID_SIZEHEADER_EXAMPLE_INVALID_TYPEHEADER_EXAMPLE_UNREACHABLEHTTP 400

La plantilla no cumple las reglas de Meta: idioma desconocido, faltan ejemplos de variables, medio de cabecera ausente, inaccesible, de tipo incorrecto o demasiado grande, o plantilla de autenticación no conforme. error.message lo precisa.

INVALID_WABAWHATSAPP_TOKEN_EXPIREDMETA_API_ERRORMETA_SUBMIT_FAILEDMETA_UPLOAD_FAILEDMETA_NOT_CONFIGUREDHTTP 400 / 502 / 503

Meta rechazó la operación en su cuenta de WhatsApp Business: waba_id ajeno, token de conexión caducado (reconecte la cuenta en el panel) o error de Meta. Reintente; si persiste, contacte con soporte.

TEMPLATE_NOT_APPROVEDMISSING_TEMPLATEMISSING_TEMPLATE_PARAMSHTTP 400

Campaña o conversación de WhatsApp: la plantilla no está aprobada, falta, o faltan variables. MISSING_TEMPLATE_PARAMS lista lo que debe indicar en template_variables (_header_media_url, _button_<i> o "static:valor").

INVALID_WABA_SENDERMISSING_LISTEMPTY_LISTLIST_IN_USEHTTP 400

Campaña: el remitente de WhatsApp no está conectado a su cuenta, falta la lista o no tiene destinatarios válidos, o la lista la usa una campaña activa (y no se puede eliminar).

ALREADY_RUNNINGCAMPAIGN_RUNNINGCAMPAIGN_NOT_EDITABLENOT_PAUSEDNOT_RUNNINGHTTP 400 / 409

El estado de la campaña no permite esta acción: solo se edita una campaña en borrador o programada, y una campaña en curso debe pausarse antes de eliminarla.

Verificación (OTP)

INVALID_CODEHTTP 400

Código erróneo. error.message indica los intentos restantes; deje que el usuario lo intente de nuevo.

MAX_ATTEMPTSHTTP 429

Demasiados intentos: la sesión está cerrada. Inicie una nueva con /v1/verify/send.

SESSION_NOT_FOUNDSESSION_EXPIREDINVALID_STATEINVALID_SESSION_DATAINVALID_SESSION_IDHTTP 400 / 404 / 409

Sesión desconocida, caducada, ID no válido (ej. null o indefinido), ya verificada o anulada, o creada por una clave del otro modo (sandbox/live). Inicie una nueva verificación con la misma clave para send, resend y check.

OTP_COOLDOWNOTP_RATE_LIMITHTTP 429

Se acaba de enviar un código a este número (30 s por SMS, 60 s por WhatsApp), o se enviaron demasiados en la última hora. Espere Retry-After; ofrezca «Reenviar» con /v1/verify/resend.

OTP_RESEND_LIMIT_EXCEEDEDHTTP 429

Límite máximo de reintentos de reenvío (4 envíos) alcanzado para esta sesión de verificación. Espere a que la sesión caduque antes de iniciar una nueva.

OTP_COUNTRY_PAUSEDHTTP 429

Códigos a ese país (fuera de Marruecos) suspendidos para su cuenta hasta las 00:00 UTC: se confirmaron muy pocos de los de hoy, señal de tráfico automatizado. Retry-After indica los segundos restantes.

COUNTRY_NOT_ALLOWEDHTTP 403

Los códigos a ese país no están abiertos para su cuenta o su app Verify. Contacte con soporte para abrirlo.

WHATSAPP_UNAVAILABLEDELIVERY_FAILEDINVALID_CODE_TYPEBAD_GATEWAYHTTP 400 / 502 / 503

El código no pudo salir por WhatsApp (no disponible para la cuenta, rechazado o código no numérico). No se cobró nada; reenvíe con "channel": "sms".

HLR_UNAVAILABLEHTTP 503

La consulta HLR (/v1/hlr) no pudo realizarse: servicio momentáneamente no disponible o limitado. Reintente en unos segundos; no se cobró nada. Un 502 BAD_GATEWAY en esta ruta significa que la red no respondió — misma regla, no se cobra nada.

LOOKUP_FAILEDLOOKUP_UNAVAILABLEHTTP 500 / 503

La búsqueda de operador (/v1/verify/lookup) falló o no está disponible. Reintente más tarde; no se cobró nada.

Límites e idempotencia

RATE_LIMITEDTOO_MANY_REQUESTSRATE_LIMIT_EXCEEDEDHTTP 429

Se superó el límite de peticiones por minuto de la clave. Espere Retry-After (segundos); reintentar antes solo consume la siguiente ventana.

INVALID_IDEMPOTENCY_KEYHTTP 400

Idempotency-Key supera los 255 caracteres. Use un UUID o un identificador de negocio corto.

IDEMPOTENCY_IN_FLIGHTHTTP 409

La petición original con esta clave sigue en curso. Espere Retry-After y reintente con la misma clave.

IDEMPOTENCY_KEY_REUSEDHTTP 422

Esta Idempotency-Key ya se usó con otro cuerpo de petición. Una clave designa una sola petición: genere una nueva.

REPLAY_UNAVAILABLEHTTP 422

Esta entrega de webhook no tiene contenido guardado para reenviar. No se puede repetir.

Cuenta y facturación

LIMIT_EXCEEDEDHTTP 403

Se alcanzó el número máximo de claves API o webhooks de su plan. Revoque uno o cambie de plan.

INVALID_AMOUNTINVALID_PACKINVALID_PAYMENT_METHODUNSUPPORTED_METHODALREADY_COMPLETEDTRANSACTION_CANCELLEDRECEIPT_NOT_AVAILABLEINVOICE_NOT_AVAILABLESTRIPE_INVOICES_UNAVAILABLENO_STRIPE_CUSTOMERINVALID_THRESHOLDINVALID_CYCLEINVALID_CYCLE_LIMITNO_PAID_TOPUPNO_FIELDSHTTP 400 / 403 / 404 / 409 / 410

Recarga o recarga automática: importe, umbral, límite, ciclo, paquete o medio de pago no válido, se requiere primera recarga manual, transacción cancelada o ya completada, o factura no disponible (aún).

STRIPE_ERRORPORTAL_ERRORCUSTOMER_CREATION_FAILEDSTRIPE_NOT_CONFIGUREDPAYMENT_METHOD_NOT_CONFIGUREDNOT_AVAILABLEINVOICE_TEMPORARILY_UNAVAILABLEINVOICE_BEING_GENERATEDINVALID_SIGNATUREHTTP 401 / 500 / 502 / 503

No se pudo obtener la página de pago o de factura de nuestro lado. No se cobró nada; reintente en un momento.

Lado servidor

INTERNAL_ERRORDB_ERRORSERVICE_UNAVAILABLEBAD_REQUESTHTTP 400 / 500 / 503

Un error de nuestro lado. Reintente con espera creciente (y la misma Idempotency-Key para un envío); contacte con soporte si persiste.

Códigos de fallo de entrega

Un mensaje aceptado (202) aún puede fallar. Su error_code — en GET /v1/messages/{id} y en el webhook message.failed — es uno de estos. Un mensaje de WhatsApp rechazado antes de salir se reembolsa automáticamente.

META_WINDOW_EXPIRED

Más de 24 h desde el último mensaje del contacto. Envíe una plantilla aprobada o un SMS.

META_UNDELIVERABLE

El número no es localizable en WhatsApp. Sin reintento automático: use SMS.

META_MARKETING_LIMIT

Límite de mensajes de marketing por destinatario de WhatsApp. No reenvíe de inmediato.

META_RATE_LIMITED / META_PAIR_RATE_LIMITED

Se superó el caudal del número, o demasiados mensajes a este contacto. Falló tras reintentos automáticos; reduzca el ritmo.

META_POLICY_BLOCKED / META_ACCOUNT_LOCKED / META_PAYMENT_ISSUE / META_PHONE_NOT_REGISTERED

El número o la cuenta de WhatsApp emisora está restringida, bloqueada, sin medio de pago de Meta válido o no registrada. Revise WhatsApp Manager; sus fondos de EnvoiSMS no son la causa.

META_TEMPLATE_NOT_FOUND / META_TEMPLATE_PARAM_MISMATCH / META_TEMPLATE_PARAM_FORMAT

Plantilla inexistente en ese idioma o aún no aprobada, número de variables incorrecto, o variable con formato erróneo (medio de cabecera, botón).

META_UNSUPPORTED_TYPE

Este tipo de mensaje no es compatible con WhatsApp para este destinatario.

META_GENERIC_ERROR / META_SERVICE_UNAVAILABLE

Error temporal de WhatsApp que persistió tras los reintentos automáticos. Reenvíe en unos minutos.

CASCADE_TIMEOUT

Cascada: sin acuse de entrega en cascade_timeout; el mensaje pasó al siguiente canal (evento message.fallback).

¿Le ha sido útil esta sección?

Límites

Límites#

Peticiones

Por clave y minuto. Valor por defecto de una clave nueva según el plan: Starter 60, Business 180, Pro 500, Enterprise 1 200. Por encima: 429 RATE_LIMITED con Retry-After.

Envío masivo

Hasta 10 000 mensajes por llamada a POST /v1/messages/bulk.

Verificación

Código de 4 a 8 dígitos (6 por defecto), válido de 60 s a 30 min (10 min por defecto). Reenvío posible tras 30 s por SMS, 60 s por WhatsApp.

Idempotencia

Idempotency-Key de hasta 255 caracteres, conservada 24 h por cuenta.

SDK

Bibliotecas cliente#

Opcionales: cada ejemplo de esta página funciona con HTTP simple, sin dependencias.

Node.jsTypeScript · JavaScript
npm install envoisms
Repositorio GitHub
PythonPython 3.8+
pip install envoisms
Repositorio GitHub
PHPPHP 8.1+
composer require envoisms/envoisms-php
Repositorio GitHub
Laravel OTPLaravel 9–11
composer require envoisms/laravel-otp
Repositorio GitHub
GoGo 1.21+
go get github.com/n4ouri/envoisms-go
Repositorio GitHub
MCP serverClaude, Cursor, agents
npx -y @envoisms/mcp-server
Repositorio GitHub
DocsMessages/messages
POSThttps://api.envoisms.ma/v1/messages
Bearer

Enviar un mensaje

Pone en cola un SMS o un mensaje de WhatsApp a un destinatario y descuenta su precio. Responde 202 con el identificador del mensaje; el resultado de la entrega llega después por webhook o mediante GET /v1/messages/:id.

Cuerpo de la petición (JSON)
CampoTipoEstadoDescripción
tostringRequeridoNúmero del destinatario en formato E.164 (+212612345678).
messagestringRequeridoTexto del mensaje, 1 600 caracteres como máximo (el exceso se corta). Alias: body. Opcional en WhatsApp cuando la petición lleva una plantilla, un medio, una reacción, contactos, una ubicación o un mensaje interactivo.
sender_idstringOpcionalSMS: el nombre que ve el destinatario (letras, dígitos y espacios, de 3 a 11 caracteres; se rechaza un encabezado genérico como INFO o el nombre de un banco). Si se omite, el SMS sale con nuestro remitente compartido EnvoiSMS a 0,792 MAD. Su propio nombre funciona sin trámite a 1,70 MAD mientras no esté validado con su RC, y a la tarifa estándar una vez validado. WhatsApp: el número conectado que envía. Alias: from, sender.
channelstringOpcional"sms" (por defecto) o "whatsapp". "whatsapp" envía desde su propio número de WhatsApp Business conectado (si no, 403 WHATSAPP_NOT_CONNECTED). Salvo que sea una plantilla, un mensaje de WhatsApp solo se acepta si el contacto le escribió en las últimas 24 horas; si no, 400 OUT_OF_24H_WINDOW, sin cargo.
cascadeboolean | string[]Opcionaltrue: intenta WhatsApp y pasa a SMS — de inmediato si WhatsApp rechaza el mensaje o informa de su fallo, o si no llega ningún acuse dentro de cascade_timeout. También acepta una lista ordenada, por ejemplo ["whatsapp", "sms"]. Solo se factura el envío que realmente salió; un WhatsApp aún sin acuse sí salió, así que se factura junto con su SMS de respaldo y se reembolsa si WhatsApp informa después de su fallo.
cascade_timeoutintegerOpcionalCon cascade: segundos de espera de un acuse de entrega antes del canal siguiente, de 30 a 43200. Por defecto: 120. Dé a su plantilla de WhatsApp un tiempo de vida (TTL) no mayor que este valor, para que un teléfono que vuelve a conectarse no reciba ambos mensajes.
templateobjectOpcional(WhatsApp) Plantilla aprobada de su cuenta: {"name", "language", "components"?}. Los valores de las variables del cuerpo van en metadata.variables. Único tipo de mensaje permitido fuera de la ventana de 24 horas; se tarifica según la categoría de la plantilla.
metadataobjectOpcionalPares clave-valor guardados con el mensaje y devueltos en los webhooks (no en GET /v1/messages). Claves que lee la plataforma: variables (valores de la plantilla), media_url y media_type (encabezado multimedia de la plantilla), purpose: "otp" (ver notas).
buttonsarrayOpcional(WhatsApp) Hasta 3 botones {"type", "text"}; text de 25 caracteres como máximo. type "copy" exige copy_code, "url" exige url (http o https), "call" exige phone_number.
interactiveobjectOpcional(WhatsApp) Mensaje interactivo en el formato de WhatsApp: lista ("list"), botones de respuesta ("button", 1 a 3), formulario ("flow"), botón de enlace ("cta_url") o solicitud de ubicación ("location_request_message").
image | video | audio | document | stickerobjectOpcional(WhatsApp) Medio por "id" (medio ya subido) o por "link" (URL https), nunca ambos. "caption" en imagen, vídeo y documento; "filename" en documento.
reactionobjectOpcional(WhatsApp) {"message_id": wamid, "emoji": "👍"} — reacciona a un mensaje de la conversación; un emoji vacío retira la reacción. No se factura.
contactsarrayOpcional(WhatsApp) Fichas de contacto (20 como máximo): name.formatted_name obligatorio; phones, emails, urls, addresses, org, birthday opcionales.
locationobjectOpcional(WhatsApp) Ubicación: {"latitude", "longitude", "name"?, "address"?}.
contextobjectOpcional(WhatsApp) {"message_id": wamid} — responde citando un mensaje anterior. Válido en todos los tipos salvo reaction.
phone_number_idstringOpcional(WhatsApp) Identificador de Meta del número conectado que envía; por defecto su número predeterminado.
Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Permiso de clave API requerido: send.
  • Idempotencia: envíe una cabecera Idempotency-Key (255 caracteres como máximo) para reintentar una petición sin enviar dos veces. Durante 24 h, la misma clave con el mismo cuerpo devuelve la respuesta original (cabecera Idempotency-Replayed: true); con otro cuerpo, 422 IDEMPOTENCY_KEY_REUSED; mientras la primera petición sigue en curso, 409 IDEMPOTENCY_IN_FLIGHT.
  • Clave sandbox (env_test_…): no se envía ni se cobra nada; cost vale 0 y la respuesta añade "sandbox": true, "simulated_cost" (el precio que habría pagado una clave live) y, si procede, "warnings" (lo que se le habría rechazado a una clave live, como una ventana de 24 h cerrada).
  • Códigos de un solo uso: /v1/verify/send gestiona todo el ciclo y sigue siendo la vía recomendada. Si genera sus propios códigos y los envía aquí, añada metadata: {"purpose": "otp"}: ante un fallo de entrega confirmado por la red hacia un número marroquí mientras el código tiene menos de 10 minutos, la plataforma lo reenvía una vez por otra ruta SMS, con el mismo identificador de mensaje y sin coste adicional.
  • Errores frecuentes: 400 INVALID_PHONE, 400 OUT_OF_24H_WINDOW, 400 SENDER_ID_GENERIC, 402 INSUFFICIENT_BALANCE, 403 WHATSAPP_NOT_CONNECTED, 403 SENDER_ID_PROTECTED_BRAND. Una petición rechazada nunca se cobra.
  • Formato: los saltos de línea (\n) funcionan en todos los canales. El SMS es texto plano; WhatsApp interpreta *negrita*, _cursiva_ y ~tachado~.
POSThttps://api.envoisms.ma/v1/messages
curl -X POST "https://api.envoisms.ma/v1/messages" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "+212612345678",
    "message": "Your order #1042 has shipped."
  }'
{
  "id": "msg_3f9a1c7e5b2d4a6f8e0c1b3d",
  "to": "+212612345678",
  "channel": "sms",
  "cascade": false,
  "status": "queued",
  "cost": { "eur": 0.072, "mad": 0.792 },
  "segments": 1,
  "created_at": "2026-10-22T09:14:03.512Z"
}
DocsMessages/messages/bulk
POSThttps://api.envoisms.ma/v1/messages/bulk
Bearer

Envío masivo

Pone en cola hasta 10 000 mensajes en una llamada, cada uno con su destinatario y su texto. Responde 202 con un batch_id, los mensajes aceptados y, si los hay, los rechazados.

Cuerpo de la petición (JSON)
CampoTipoEstadoDescripción
messagesarrayRequeridoArray de 1 a 10 000 objetos: "to" (E.164), "message" (o "body") y, opcionalmente, "metadata" y "buttons" (WhatsApp).
sender_idstringOpcionalRemitente común a todo el lote (mismas reglas que POST /v1/messages). Alias: from, sender.
channelstringOpcional"sms" (por defecto) o "whatsapp". En WhatsApp el envío masivo no lleva plantilla: cada destinatario debe estar dentro de su ventana de 24 horas.
Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Permiso de clave API requerido: send.
  • El lote se valida entero antes de enviar nada: una sola entrada inválida hace fallar la petición con 400 INVALID_MESSAGE, con la lista de las 20 primeras entradas erróneas (índice y regla). El saldo se comprueba para todo el lote (402 INSUFFICIENT_BALANCE).
  • "rejected" enumera las entradas rechazadas una a una (baja, ventana de 24 h cerrada…) con su código; no se cobran. Solo aparece si las hay. "estimated_cost" es la estimación del lote antes de los rechazos.
  • Se acepta Idempotency-Key, con las mismas reglas que POST /v1/messages.
POSThttps://api.envoisms.ma/v1/messages/bulk
curl -X POST "https://api.envoisms.ma/v1/messages/bulk" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "messages": [
      {
        "to": "+212611111111",
        "message": "Your appointment is tomorrow at 10:00."
      },
      {
        "to": "+212622222222",
        "message": "Your appointment is tomorrow at 11:30."
      }
    ]
  }'
{
  "batch_id": "batch_9a3c5e7f1b2d4c6e8a0f2b4d",
  "total": 2,
  "channel": "sms",
  "estimated_cost": { "eur": 0.144, "mad": 1.584 },
  "messages": [
    { "id": "msg_1a2b3c4d5e6f7a8b9c0d1e2f", "to": "+212611111111", "status": "queued" },
    { "id": "msg_3c4d5e6f7a8b9c0d1e2f3a4b", "to": "+212622222222", "status": "queued" }
  ],
  "rejected": [
    { "to": "+212633333333", "code": "OPTED_OUT", "message": "This number opted out of messages from your account" }
  ]
}
DocsMessages/messages/typing
POSThttps://api.envoisms.ma/v1/messages/typing
Bearer

Indicador de escritura de WhatsApp

Marca como leído un mensaje de WhatsApp recibido y muestra «escribiendo…» a su autor mientras prepara la respuesta (hasta que responda o unos 25 s). No se almacena ni se factura nada.

Cuerpo de la petición (JSON)
CampoTipoEstadoDescripción
message_idstringRequeridoIdentificador de WhatsApp (wamid) del mensaje recibido al que responde.
typing_indicatorbooleanOpcionalfalse envía solo la confirmación de lectura. Por defecto: true.
phone_number_idstringOpcionalNúmero conectado que recibió el mensaje; por defecto su número predeterminado.
Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Permiso de clave API requerido: send.
  • Con una clave sandbox no se transmite nada a WhatsApp y la respuesta es {"status": "simulated", "sandbox": true, "message_id", "typing_indicator"}.
POSThttps://api.envoisms.ma/v1/messages/typing
curl -X POST "https://api.envoisms.ma/v1/messages/typing" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "message_id": "wamid.HBgMMjEyNjEyMzQ1Njc4FQIAEhg",
    "typing_indicator": true
  }'
{
  "status": "ok",
  "message_id": "wamid.HBgLMjEyNjEyMzQ1Njc4FQIAEhgg",
  "typing_indicator": true
}
DocsMessages/messages
GEThttps://api.envoisms.ma/v1/messages
Bearer

Listar mensajes

Devuelve los mensajes de la cuenta, del más reciente al más antiguo, con filtros y paginación. format=csv exporta todo el conjunto filtrado.

Parámetros de consulta
ParámetroTipoEstadoDescripción
limitintegerOpcionalNúmero de resultados: 50 por defecto, 200 como máximo.
offsetintegerOpcionalNúmero de resultados que se omiten (paginación). Por defecto: 0.
statusstringOpcionalqueued, sent, delivered, read (WhatsApp), failed, undeliverable o unconfirmed.
channelstringOpcionalsms o whatsapp.
campaign_idstringOpcionalMensajes de una campaña (cmp_…).
from_datestringOpcionalCreados a partir de esta fecha (UTC). Escríbala AAAA-MM-DD: se compara con created_at, con formato "AAAA-MM-DD HH:MM:SS".
to_datestringOpcionalCreados hasta esta fecha (UTC), misma comparación. Para incluir un día entero, pase el día siguiente.
formatstringOpcionalcsv: descarga todo el conjunto filtrado (hasta el tope de exportación, indicado en el archivo) en lugar de una página.
Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Permiso de clave API requerido: status.
  • Una clave solo ve los mensajes de su propio modo: una clave sandbox solo lista envíos simulados, una clave live solo envíos reales.
GEThttps://api.envoisms.ma/v1/messages
curl -X GET "https://api.envoisms.ma/v1/messages?limit=50" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY"
{
  "data": [
    {
      "id": "msg_3f9a1c7e5b2d4a6f8e0c1b3d",
      "campaign_id": null,
      "to": "+212612345678",
      "channel": "sms",
      "body": "Votre commande 4512 est expédiée.",
      "sender_id": "MABOUTIQUE",
      "unicode": 0,
      "segments": 1,
      "status": "delivered",
      "error_code": null,
      "error_message": null,
      "cost_eur": 0.072,
      "cost_mad": 0.792,
      "scheduled_at": null,
      "sent_at": "2026-10-22 09:14:05",
      "delivered_at": "2026-10-22 09:14:09",
      "read_at": null,
      "failed_at": null,
      "created_at": "2026-10-22 09:14:03",
      "operator": "Maroc Telecom",
      "sandbox": 0
    }
  ],
  "limit": 50,
  "offset": 0,
  "total": 1
}
DocsMessages/messages/:id
GEThttps://api.envoisms.ma/v1/messages/:id
Bearer

Consultar un mensaje

Devuelve un mensaje y su estado de entrega. 404 NOT_FOUND si el identificador no pertenece a la cuenta o al modo de la clave.

Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Permiso de clave API requerido: status.
  • Estados: queued, sent, delivered, read (WhatsApp), failed, undeliverable, unconfirmed. "unconfirmed" significa que el operador no confirmó ni negó la entrega; un acuse que llegue más tarde aún puede sustituirlo.
  • El campo metadata enviado no se devuelve aquí; acompaña a los webhooks.
GEThttps://api.envoisms.ma/v1/messages/:id
curl -X GET "https://api.envoisms.ma/v1/messages/{id}" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY"
{
  "id": "msg_3f9a1c7e5b2d4a6f8e0c1b3d",
  "campaign_id": null,
  "to": "+212612345678",
  "channel": "sms",
  "body": "Votre commande 4512 est expédiée.",
  "sender_id": "MABOUTIQUE",
  "unicode": 0,
  "segments": 1,
  "status": "delivered",
  "error_code": null,
  "error_message": null,
  "cost_eur": 0.072,
  "cost_mad": 0.792,
  "scheduled_at": null,
  "sent_at": "2026-10-22 09:14:05",
  "delivered_at": "2026-10-22 09:14:09",
  "read_at": null,
  "failed_at": null,
  "created_at": "2026-10-22 09:14:03",
  "operator": "Maroc Telecom",
  "sandbox": 0
}
DocsTemplates/templates
GEThttps://api.envoisms.ma/v1/templates
Bearer

Listar plantillas

Devuelve las plantillas activas de la cuenta (SMS y WhatsApp), de la más reciente a la más antigua, sea cual sea su estado.

Parámetros de consulta
ParámetroTipoEstadoDescripción
channelstringOpcionalsms, whatsapp, telegram o any.
categorystringOpcionalmarketing, utility o authentication.
Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Permiso de clave API requerido: send.
  • "variables" es una cadena JSON que describe la estructura de la plantilla: variables, valores de ejemplo, encabezado, pie, botones, carrusel y revisión.
  • Estados de WhatsApp: pending_admin (revisión de EnvoiSMS), pending_meta, approved, rejected, paused, disabled, in_appeal, flagged, deleted_on_meta. Una plantilla SMS queda approved al crearse.
GEThttps://api.envoisms.ma/v1/templates
curl -X GET "https://api.envoisms.ma/v1/templates" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY"
{
  "data": [
    {
      "id": "tpl_4c1e9a7b3d5f2e8a6c0b1d3f",
      "account_id": "acct_7b2e4c6a8d0f1e3b5c7a9d2f",
      "name": "order_shipped",
      "channel": "whatsapp",
      "body": "Bonjour {{1}}, votre commande {{2}} est expédiée.",
      "variables": "{\"variables\":[\"1\",\"2\"],\"parameter_format\":\"positional\",\"sample_values\":{\"1\":\"Amine\",\"2\":\"4512\"},\"header_type\":\"none\",\"header_text\":null,\"footer\":null,\"buttons\":[]}",
      "category": "utility",
      "active": 1,
      "created_at": "2026-10-20 14:02:11",
      "status": "approved",
      "rejection_reason": null,
      "meta_template_id": "1203948576102938",
      "language": "fr",
      "quality_score": "GREEN",
      "quality_updated_at": "2026-10-21 08:00:00",
      "waba_id": null
    }
  ]
}
DocsTemplates/templates
POSThttps://api.envoisms.ma/v1/templates
Bearer

Crear una plantilla

Crea una plantilla SMS (utilizable de inmediato) o de WhatsApp (revisada por EnvoiSMS y luego enviada a Meta en su propia cuenta de WhatsApp Business). Responde 201 con la fila creada.

Cuerpo de la petición (JSON)
CampoTipoEstadoDescripción
namestringRequeridoNombre de la plantilla. Se pasa a minúsculas; todo carácter distinto de a-z, 0-9 y _ se convierte en _.
channelstringRequeridosms, whatsapp, telegram o any.
bodystringRequeridoTexto de la plantilla. WhatsApp: 1 024 caracteres como máximo; variables todas numeradas desde {{1}} y consecutivas, o todas con nombre en minúsculas ({{nombre}}); nunca al principio ni al final, nunca pegadas. No se exige para una plantilla de WhatsApp "authentication", cuyo texto fija Meta.
categorystringOpcionalmarketing (por defecto), utility o authentication. otp equivale a authentication y transactional a utility. Meta puede reasignar la categoría en la revisión.
languagestringOpcional(WhatsApp) Código de idioma de Meta, por ejemplo fr, ar, en_US, es. Por defecto: fr.
sample_valuesobject | string[]Opcional(WhatsApp) Un valor de ejemplo por variable, exigido por Meta: {"1": "Amine"} o ["Amine"]. Sin él, 400 MISSING_SAMPLE_VALUES.
variablesstring[]Opcional(SMS) Nombres de las variables del texto, guardados tal cual.
header_typestringOpcional(WhatsApp) none (por defecto), text, image, video o document. Un encabezado text lleva header_text (60 caracteres, una variable como máximo, con header_sample); un encabezado multimedia exige header_example_url.
header_text | header_sample | header_example_urlstringOpcional(WhatsApp) Texto del encabezado; valor de ejemplo de su variable; enlace https público a un archivo de ejemplo para un encabezado de imagen, vídeo o documento (400 MISSING_HEADER_EXAMPLE si falta).
footerstringOpcional(WhatsApp) Pie, 60 caracteres como máximo.
buttonsarrayOpcional(WhatsApp) Hasta 10 botones {"type", "text"} (25 caracteres). Tipos: quick_reply; url (con url http(s), y url_example si el enlace termina en una variable); phone_number (con phone_number); copy_code (con copy_code); flow (con flow_id, el identificador de uno de sus WhatsApp Flows publicados, y navigate_screen, "LEAD" por defecto).
carouselobjectOpcional(WhatsApp, marketing o utility) {"header_type": "image" | "video", "cards": [...]}: de 2 a 10 tarjetas, cada una {"header_example_url" (https), "body" (160 caracteres, sin variable), "buttons" (1 o 2, quick_reply o url sin variable)}. Todas las tarjetas llevan los mismos botones. Una plantilla carrusel no tiene encabezado, pie ni botones propios.
add_security_recommendation | code_expiration_minutes | otp_type | supported_appsmixedOpcional(WhatsApp, authentication) Aviso de seguridad de Meta (booleano); validez del código, de 1 a 90 minutos; COPY_CODE (por defecto), ONE_TAP o ZERO_TAP; supported_apps [{"package_name", "signature_hash" (11 caracteres)}], exigido para ONE_TAP y ZERO_TAP. ZERO_TAP exige además zero_tap_terms_accepted: true.
waba_idstringOpcional(WhatsApp) Cuenta de WhatsApp Business de destino cuando la cuenta tiene varias; debe ser una de sus cuentas conectadas (si no, 400 INVALID_WABA).
Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Permiso de clave API requerido: send.
  • Revisión: una plantilla de WhatsApp se crea en pending_admin. Unos controles automáticos (suplantación de un banco, enlace de phishing, alojamiento de enlaces desechables, cuenta inactiva) se ejecutan al instante; si no encuentran nada, la plantilla se envía a Meta (pending_meta) y la respuesta ya lo muestra. Si no, queda en pending_admin para una revisión humana. Solo Meta la aprueba (approved) y fija su categoría final.
  • Errores: 400 MISSING_FIELD, INVALID_CHANNEL, INVALID_LANGUAGE, INVALID_META_TEMPLATE, MISSING_SAMPLE_VALUES, MISSING_HEADER_EXAMPLE, INVALID_AUTH_TEMPLATE, INVALID_WABA; 409 TEMPLATE_EXISTS (mismo nombre e idioma ya activos; una plantilla rechazada del mismo nombre se sustituye).
POSThttps://api.envoisms.ma/v1/templates
curl -X POST "https://api.envoisms.ma/v1/templates" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "order_shipped",
    "channel": "whatsapp",
    "category": "utility",
    "language": "fr",
    "body": "Bonjour {{1}}, votre commande {{2}} est expédiée.",
    "sample_values": [
      "Karim",
      "#1042"
    ]
  }'
{
  "id": "tpl_8e2a4c6b1d3f5a7c9e0b2d4f",
  "account_id": "acct_7b2e4c6a8d0f1e3b5c7a9d2f",
  "name": "catalogue_automne",
  "channel": "whatsapp",
  "body": "Découvrez notre sélection d’automne, {{1}} !",
  "variables": "{\"header_type\":\"none\",\"header_text\":null,\"footer\":null,\"buttons\":[],\"carousel\":{\"header_type\":\"image\",\"cards\":[{\"header_example_url\":\"https://example.ma/veste.jpg\",\"body\":\"Veste en laine\",\"buttons\":[{\"type\":\"url\",\"text\":\"Voir\",\"url\":\"https://example.ma/veste\"}]},{\"header_example_url\":\"https://example.ma/pull.jpg\",\"body\":\"Pull en coton\",\"buttons\":[{\"type\":\"url\",\"text\":\"Voir\",\"url\":\"https://example.ma/pull\"}]}]},\"variables\":[\"1\"],\"parameter_format\":\"positional\",\"sample_values\":{\"1\":\"Amine\"}}",
  "category": "marketing",
  "active": 1,
  "created_at": "2026-10-22 09:20:41",
  "status": "pending_meta",
  "rejection_reason": null,
  "meta_template_id": "1203948576102944",
  "language": "fr",
  "quality_score": null,
  "quality_updated_at": null,
  "waba_id": null
}
DocsTemplates/templates/:id
GEThttps://api.envoisms.ma/v1/templates/:id
Bearer

Consultar una plantilla

Devuelve una plantilla activa de la cuenta, con su estado y, si fue rechazada, rejection_reason. Si no, 404 NOT_FOUND.

Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Permiso de clave API requerido: send.
GEThttps://api.envoisms.ma/v1/templates/:id
curl -X GET "https://api.envoisms.ma/v1/templates/{id}" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY"
{
  "id": "tpl_4c1e9a7b3d5f2e8a6c0b1d3f",
  "account_id": "acct_7b2e4c6a8d0f1e3b5c7a9d2f",
  "name": "order_shipped",
  "channel": "whatsapp",
  "body": "Bonjour {{1}}, votre commande {{2}} est expédiée.",
  "variables": "{\"variables\":[\"1\",\"2\"],\"parameter_format\":\"positional\",\"sample_values\":{\"1\":\"Amine\",\"2\":\"4512\"},\"header_type\":\"none\",\"header_text\":null,\"footer\":null,\"buttons\":[]}",
  "category": "utility",
  "active": 1,
  "created_at": "2026-10-20 14:02:11",
  "status": "approved",
  "rejection_reason": null,
  "meta_template_id": "1203948576102938",
  "language": "fr",
  "quality_score": "GREEN",
  "quality_updated_at": "2026-10-21 08:00:00",
  "waba_id": null
}
DocsTemplates/templates/:id
PATCHhttps://api.envoisms.ma/v1/templates/:id
Bearer

Modificar una plantilla

Modifica una plantilla con los mismos campos que la creación; solo cambian los campos enviados. Responde con la fila actualizada.

Cuerpo de la petición (JSON)
CampoTipoEstadoDescripción
name | body | category | language | sample_values | header_* | footer | buttons | carouselmixedOpcionalMismos campos y reglas que POST /v1/templates. Una plantilla SMS solo acepta name, body, category y variables.
Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Permiso de clave API requerido: send.
  • WhatsApp, plantilla aún no enviada a Meta: la modificación la devuelve a revisión (pending_admin y luego los controles automáticos). Plantilla ya en Meta: la modificación se envía a Meta y la devuelve a pending_meta; el nombre y el idioma ya no cambian (400 TEMPLATE_IMMUTABLE_FIELD), ni la categoría de una plantilla aprobada; ninguna modificación durante pending_meta, in_appeal o disabled (409 TEMPLATE_EDIT_NOT_ALLOWED).
PATCHhttps://api.envoisms.ma/v1/templates/:id
curl -X PATCH "https://api.envoisms.ma/v1/templates/{id}" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY"
{
  "id": "tpl_4c1e9a7b3d5f2e8a6c0b1d3f",
  "account_id": "acct_7b2e4c6a8d0f1e3b5c7a9d2f",
  "name": "order_shipped",
  "channel": "whatsapp",
  "body": "Bonjour {{1}}, votre commande {{2}} est en route.",
  "variables": "{\"variables\":[\"1\",\"2\"],\"parameter_format\":\"positional\",\"sample_values\":{\"1\":\"Amine\",\"2\":\"4512\"},\"header_type\":\"none\",\"header_text\":null,\"footer\":null,\"buttons\":[]}",
  "category": "utility",
  "active": 1,
  "created_at": "2026-10-20 14:02:11",
  "status": "pending_meta",
  "rejection_reason": null,
  "meta_template_id": "1203948576102938",
  "language": "fr",
  "quality_score": "GREEN",
  "quality_updated_at": "2026-10-21 08:00:00",
  "waba_id": null
}
DocsTemplates/templates/:id
DELETEhttps://api.envoisms.ma/v1/templates/:id
Bearer

Eliminar una plantilla

Desactiva la plantilla. Una plantilla de WhatsApp ya en Meta también se elimina allí; meta_deleted indica si Meta lo hizo y meta_note por qué no.

Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Permiso de clave API requerido: send.
DELETEhttps://api.envoisms.ma/v1/templates/:id
curl -X DELETE "https://api.envoisms.ma/v1/templates/{id}" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY"
{
  "deleted": true,
  "id": "tpl_4c1e9a7b3d5f2e8a6c0b1d3f",
  "meta_deleted": true
}
DocsTemplates/templates/:id/submit-meta
POSThttps://api.envoisms.ma/v1/templates/:id/submit-meta
Bearer

Enviar una plantilla a Meta

Envía a Meta una plantilla de WhatsApp que ya pasó la revisión de EnvoiSMS pero no está en Meta (Meta la eliminó, por ejemplo). No es una forma de saltarse la revisión.

Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Permiso de clave API requerido: send.
  • Rechazos: 409 TEMPLATE_PENDING_REVIEW (la plantilla espera la revisión de EnvoiSMS; irá a Meta una vez aprobada), 409 TEMPLATE_REJECTED_BY_REVIEW (rechazada en la revisión; modifíquela para devolverla a revisión), 409 TEMPLATE_ALREADY_ON_META, 400 INVALID_CHANNEL (no es una plantilla de WhatsApp).
POSThttps://api.envoisms.ma/v1/templates/:id/submit-meta
curl -X POST "https://api.envoisms.ma/v1/templates/{id}/submit-meta" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY"
{
  "ok": true,
  "template": {
    "id": "tpl_4c1e9a7b3d5f2e8a6c0b1d3f",
    "account_id": "acct_7b2e4c6a8d0f1e3b5c7a9d2f",
    "name": "order_shipped",
    "channel": "whatsapp",
    "body": "Bonjour {{1}}, votre commande {{2}} est expédiée.",
    "variables": "{\"variables\":[\"1\",\"2\"],\"parameter_format\":\"positional\",\"sample_values\":{\"1\":\"Amine\",\"2\":\"4512\"},\"header_type\":\"none\",\"header_text\":null,\"footer\":null,\"buttons\":[]}",
    "category": "utility",
    "active": 1,
    "created_at": "2026-10-20 14:02:11",
    "status": "pending_meta",
    "rejection_reason": null,
    "meta_template_id": "1203948576102951",
    "language": "fr",
    "quality_score": null,
    "quality_updated_at": null,
    "waba_id": null
  },
  "meta": { "id": "1203948576102951", "status": "PENDING", "category": "UTILITY" }
}
DocsTemplates/templates/sync
POSThttps://api.envoisms.ma/v1/templates/sync
Bearer

Sincronizar plantillas desde Meta

Importa las plantillas de sus cuentas de WhatsApp Business (estado, categoría, calidad). Una plantilla que Meta ya no tiene pasa a deleted_on_meta, salvo si la sincronización está incompleta (complete: false).

Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Permiso de clave API requerido: send.
POSThttps://api.envoisms.ma/v1/templates/sync
curl -X POST "https://api.envoisms.ma/v1/templates/sync" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY"
{
  "ok": true,
  "wabas": ["104857392017465"],
  "fetched": 12,
  "updated": 9,
  "inserted": 3,
  "deduped": 0,
  "marked_deleted": 1,
  "complete": true,
  "errors": []
}
DocsSender IDs/sender-ids
GEThttps://api.envoisms.ma/v1/sender-ids
Bearer

Listar Sender IDs

Devuelve sus solicitudes de Sender ID SMS y su estado, junto con un nombre sugerido a partir de su perfil.

Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Permiso de clave API requerido: sender_id.
GEThttps://api.envoisms.ma/v1/sender-ids
curl -X GET "https://api.envoisms.ma/v1/sender-ids" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY"
{
  "data": [
    {
      "id": "sid_6a2c8e4b0d1f3a5c7e9b2d4f",
      "sender_id": "MABOUTIQUE",
      "status": "approved",
      "rc_url": "/v1/sender-ids/rc/acct_7b2e4c6a8d0f1e3b5c7a9d2f/rc_1f3a5c7e9b2d4f6a8c0e2b4d.pdf",
      "note": null,
      "requested_at": "2026-10-02 10:15:00",
      "reviewed_at": "2026-10-09 16:40:00",
      "company_website": "https://maboutique.ma",
      "sample_content": "Votre commande 4512 est expédiée.",
      "message_type": "transactional",
      "estimated_volume": "5000/mois",
      "operator_emailed_at": "2026-10-02 10:20:00"
    }
  ],
  "suggestedSenderId": "MABOUTIQUE"
}
DocsSender IDs/sender-ids
POSThttps://api.envoisms.ma/v1/sender-ids
Bearer

Solicitar un Sender ID

Presenta una solicitud de Sender ID SMS acompañada del registro mercantil en PDF. Cuerpo multipart/form-data. Responde 201 (200 si se actualiza una solicitud existente) con el estado pending.

Cuerpo de la petición (JSON)
CampoTipoEstadoDescripción
sender_idstringRequeridoNombre solicitado: letras sin acento, dígitos y espacios, 11 caracteres como máximo, al menos una letra. Se rechaza un encabezado genérico (INFO, ALERT…) o el nombre de una institución protegida.
filefileRequeridoRegistro mercantil, PDF de 5 MB como máximo.
message_typestringOpcionaltransactional (por defecto) o promotional.
company_website | sample_content | estimated_volumestringOpcionalSitio web de la empresa, mensaje de ejemplo y volumen estimado, transmitidos a los operadores con la solicitud.
Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Permiso de clave API requerido: sender_id.
  • Errores: 400 INVALID_FORM_DATA, MISSING_FIELD, INVALID_FIELD, SENDER_ID_GENERIC, INVALID_FILE_TYPE, FILE_TOO_LARGE; 403 SENDER_ID_PROTECTED_BRAND; 409 SENDER_ID_PENDING o SENDER_ID_APPROVED.
POSThttps://api.envoisms.ma/v1/sender-ids
curl -X POST "https://api.envoisms.ma/v1/sender-ids" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY"
{
  "id": "sid_6a2c8e4b0d1f3a5c7e9b2d4f",
  "sender_id": "MABOUTIQUE",
  "status": "pending",
  "rc_url": "/v1/sender-ids/rc/acct_7b2e4c6a8d0f1e3b5c7a9d2f/rc_1f3a5c7e9b2d4f6a8c0e2b4d.pdf"
}
DocsWhatsApp/whatsapp/waba-connection
GEThttps://api.envoisms.ma/v1/whatsapp/waba-connection
Bearer

Conexión de WhatsApp Business

Devuelve el estado de sus números de WhatsApp Business conectados y la lista de control del número predeterminado (readiness): lo que, según Meta, le impide enviar. Solo lectura; no se devuelve ningún secreto.

Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Cualquier clave API válida: no se exige ningún permiso concreto.
  • readiness: can_send vale AVAILABLE, LIMITED, BLOCKED o unknown (Meta no respondió); blockers enumera los motivos de Meta (entity, reason, resolution); payment_method vale missing cuando Meta indica que falta un medio de pago, unchecked en otro caso; business_verification es el estado de verificación de la empresa en Meta. readiness vale null sin número conectado. Lo que Meta no dijo se devuelve null, nunca como un valor sano por defecto.
  • Conectar, desconectar o reconfigurar un número, y toda escritura bajo /v1/whatsapp (perfil, ajustes, herramientas), se hace desde una sesión del panel: una clave API recibe 403 FORBIDDEN. Solo POST /v1/whatsapp/lookup-phone y POST /v1/whatsapp/media/upload siguen abiertos a las claves.
GEThttps://api.envoisms.ma/v1/whatsapp/waba-connection
curl -X GET "https://api.envoisms.ma/v1/whatsapp/waba-connection" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY"
{
  "readiness": {
    "waba_id": "104857392017465",
    "payment_method": "unchecked",
    "can_send": "AVAILABLE",
    "blockers": [],
    "business_verification": "verified",
    "approved_templates": 4,
    "checked_at": "2026-10-22T08:00:12.000Z"
  },
  "connected": true,
  "mode": "custom_waba",
  "gateway_number": "+212 708-789095",
  "waba_id": "104857392017465",
  "phone_number_id": "109283746501928",
  "phone_number": "+212522000000",
  "name": "Ma Boutique",
  "status": "connected",
  "error": null,
  "quality_rating": "GREEN",
  "messaging_limit_tier": "TIER_1K",
  "connected_at": "2026-09-30 11:02:44",
  "meta_app_id": "1029384756102938",
  "meta_config_id": "2039485761029384",
  "preverified_phone_ids": [],
  "connections": [
    {
      "id": "5f1c9e3a7b2d4f6a8c0e1b3d5f7a9c2e",
      "waba_id": "104857392017465",
      "phone_number_id": "109283746501928",
      "phone_number": "+212522000000",
      "verified_name": "Ma Boutique",
      "status": "connected",
      "is_default": true,
      "webhook_url": null,
      "settings": {},
      "created_at": "2026-09-30 11:02:44",
      "updated_at": "2026-10-20 08:15:02"
    }
  ]
}
DocsWhatsApp/whatsapp/profile
GEThttps://api.envoisms.ma/v1/whatsapp/profile
Bearer

Perfil de WhatsApp Business

Devuelve el perfil público de uno de sus números tal como lo tiene Meta: acerca de, dirección, descripción, correo, foto, sitios web, sector.

Parámetros de consulta
ParámetroTipoEstadoDescripción
phone_number_idstringOpcionalNúmero conectado que se consulta; por defecto su número predeterminado.
Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Cualquier clave API válida: no se exige ningún permiso concreto.
  • El perfil se actualiza desde el panel; con una clave API, POST /v1/whatsapp/profile responde 403 FORBIDDEN.
GEThttps://api.envoisms.ma/v1/whatsapp/profile
curl -X GET "https://api.envoisms.ma/v1/whatsapp/profile" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY"
{
  "ok": true,
  "phone_number_id": "109283746501928",
  "profile": {
    "about": "Service client",
    "address": "Casablanca, Maroc",
    "description": "Prêt-à-porter, livraison partout au Maroc.",
    "email": "contact@maboutique.ma",
    "profile_picture_url": "https://pps.whatsapp.net/v/t61/...",
    "websites": ["https://maboutique.ma"],
    "vertical": "APPAREL"
  }
}
DocsVerify/verify/send
POSThttps://api.envoisms.ma/v1/verify/send
Bearer

Enviar un código de verificación

Genera un código de un solo uso, lo envía y abre una sesión de verificación. No guarda ningún código: valide lo que escribe el usuario con POST /v1/verify/check. Responde 202.

Cuerpo de la petición (JSON)
CampoTipoEstadoDescripción
tostringRequeridoDestinatario en formato E.164.
channelstringOpcional"sms" (por defecto) o "whatsapp". En WhatsApp el código sale como plantilla de autenticación — desde su propio número si ha conectado uno con una plantilla de autenticación aprobada, si no desde el número de EnvoiSMS — y pasa a SMS si Meta rechaza el envío.
app_idstringOpcionalAplicación Verify (vra_…) creada en el panel: fija longitud, validez, intentos, canales y cascada, y sustituye a code_length y expiry.
code_lengthintegerOpcionalLongitud del código, de 4 a 8 dígitos. Por defecto: 6.
expiryintegerOpcionalValidez del código en segundos, de 60 a 1800. Por defecto: 600. En WhatsApp, limitada a 600.
brandstringOpcional(SMS) Nombre mostrado en el mensaje, 32 caracteres como máximo. Por defecto: EnvoiSMS. Un nombre genérico se sustituye por ENVOISMS; se rechaza el nombre de una institución protegida (403 SENDER_ID_PROTECTED_BRAND).
templatestringOpcional(SMS) Texto personalizado con {{code}} y {{brand}}.
otp_button_textstringOpcional(WhatsApp) Etiqueta del botón de copia, 25 caracteres como máximo.
otp_type | package_name | signature_hashstringOpcional(WhatsApp) copy_code (por defecto), one_tap, zero_tap o url; para one_tap y zero_tap, el nombre del paquete Android y el hash de firma de la aplicación que recibe el código.
web_otp_domain | app_hashstringOpcional(SMS) Dominio para el autocompletado WebOTP (añade «@dominio #code» al SMS); hash de 11 caracteres de la aplicación Android para la API SMS Retriever.
auto_cascadebooleanOpcionalEnvía también el SMS de respaldo cuando el plazo del primer canal expira sin entrega. Sustituye el ajuste de la aplicación Verify.
Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Permiso de clave API requerido: verify.
  • En WhatsApp la respuesta lleva "channel": "whatsapp", "resend_after_seconds": 60, "fallback_channels": ["sms", "whatsapp"] y "cost" ({eur, mad}), sin message_id. Con una clave sandbox no se envía ni se cobra nada y la respuesta añade "sandbox": true, "simulated": true y "sandbox_code", el código que se envía a /v1/verify/check.
  • Límites por número: un envío cada 30 s por SMS y 60 s por WhatsApp (429 OTP_COOLDOWN, con Retry-After), 5 por hora y 10 por día (429 OTP_RATE_LIMIT); 200 envíos por día y por cuenta.
  • Autocompletado: en su sitio, añada autocomplete="one-time-code" e inputmode="numeric" al campo del código.
POSThttps://api.envoisms.ma/v1/verify/send
curl -X POST "https://api.envoisms.ma/v1/verify/send" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "+212612345678",
    "brand": "MyApp"
  }'
{
  "session_id": "vrf_7e2a9c4b1d3f5a7c9e0b2d4f",
  "message_id": "msg_2b4d6f8a0c1e3b5d7f9a1c3e",
  "to": "+212612345678",
  "channel": "sms",
  "expires_at": "2026-10-22T09:24:03.000Z",
  "resend_after_seconds": 30,
  "fallback_channels": [],
  "status": "sent"
}
DocsVerify/verify/resend
POSThttps://api.envoisms.ma/v1/verify/resend
Bearer

Reenviar el código

Reenvía el código de una sesión aún abierta, por SMS o WhatsApp, una vez pasado el tiempo de espera. El código no cambia. Responde 200.

Cuerpo de la petición (JSON)
CampoTipoEstadoDescripción
session_idstringRequeridoSesión devuelta por /v1/verify/send (vrf_…).
channelstringOpcional"sms" (por defecto) o "whatsapp".
Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Permiso de clave API requerido: verify.
  • El tiempo de espera corre desde el último envío real: 30 s tras un SMS, 60 s tras un WhatsApp. GET /v1/verify/:id da la cuenta atrás (resend_available_in_seconds) y el 429 OTP_COOLDOWN lleva Retry-After. La ventana es propia de su cuenta y del número.
  • Errores: 404 SESSION_NOT_FOUND (desconocida, expirada o del otro modo de clave), 400 SESSION_EXPIRED, 400 INVALID_STATE (sesión ya verificada o cerrada), 400 UNSUPPORTED_CHANNEL, 429 OTP_COOLDOWN. Con una clave sandbox la respuesta añade "sandbox": true, "simulated": true y "sandbox_code".
POSThttps://api.envoisms.ma/v1/verify/resend
curl -X POST "https://api.envoisms.ma/v1/verify/resend" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "session_id": "vrf_…",
    "channel": "sms"
  }'
{
  "session_id": "vrf_7e2a9c4b1d3f5a7c9e0b2d4f",
  "message_id": "msg_8c0e2b4d6f1a3c5e7b9d1f3a",
  "to": "+212612345678",
  "channel": "sms",
  "status": "sent",
  "resend_after_seconds": 30,
  "expires_at": "2026-10-22T09:24:03.000Z"
}
DocsVerify/verify/check
POSThttps://api.envoisms.ma/v1/verify/check
Bearer

Comprobar un código

Compara el código que escribió el usuario con el de la sesión. Un código correcto cierra la sesión: una segunda comprobación responde 404.

Cuerpo de la petición (JSON)
CampoTipoEstadoDescripción
session_idstringRequeridoSesión devuelta por /v1/verify/send.
codestringRequeridoEl código que escribió el usuario.
Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Permiso de clave API requerido: status.
  • Errores: 400 INVALID_CODE (el mensaje indica los intentos restantes), 429 MAX_ATTEMPTS (la sesión pasa a failed), 404 SESSION_NOT_FOUND — también para una sesión expirada o ya verificada. /v1/verify nunca devuelve EXPIRED_CODE.
POSThttps://api.envoisms.ma/v1/verify/check
curl -X POST "https://api.envoisms.ma/v1/verify/check" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "session_id": "vrf_…",
    "code": "483921"
  }'
{
  "session_id": "vrf_7e2a9c4b1d3f5a7c9e0b2d4f",
  "verified": true,
  "verified_at": "2026-10-22T09:15:12.408Z"
}
DocsVerify/verify/void
POSThttps://api.envoisms.ma/v1/verify/void
Bearer

Anular una sesión

Cierra una sesión pendiente: el código enviado deja de aceptarse. También disponible como POST /v1/verify/:id/void.

Cuerpo de la petición (JSON)
CampoTipoEstadoDescripción
session_idstringRequeridoSesión que se anula (omítala si el identificador va en la ruta).
Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Permiso de clave API requerido: status.
  • Errores: 404 SESSION_NOT_FOUND, 409 INVALID_STATE (sesión ya verificada, fallida o expirada). Si la aplicación Verify tiene callback_url, se le notifica (expired).
POSThttps://api.envoisms.ma/v1/verify/void
curl -X POST "https://api.envoisms.ma/v1/verify/void" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "session_id": "vrf_…"
  }'
{
  "session_id": "vrf_7e2a9c4b1d3f5a7c9e0b2d4f",
  "status": "expired",
  "voided": true
}
DocsVerify/verify/:id
GEThttps://api.envoisms.ma/v1/verify/:id
Bearer

Consultar una sesión

Devuelve el estado de una sesión de verificación: estado, intentos, cuenta atrás antes del reenvío y resultado de entrega de cada envío.

Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Permiso de clave API requerido: status.
  • status vale pending, verified, failed o expired. 404 NOT_FOUND si la sesión no pertenece a la cuenta.
GEThttps://api.envoisms.ma/v1/verify/:id
curl -X GET "https://api.envoisms.ma/v1/verify/{id}" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY"
{
  "id": "vrf_7e2a9c4b1d3f5a7c9e0b2d4f",
  "session_id": "vrf_7e2a9c4b1d3f5a7c9e0b2d4f",
  "to": "+212612345678",
  "channel": "sms",
  "status": "pending",
  "attempts_used": 1,
  "attempts_remaining": 2,
  "max_attempts": 3,
  "resend_available_in_seconds": 0,
  "can_resend_now": true,
  "expires_at": "2026-10-22T09:24:03.000Z",
  "verified_at": null,
  "created_at": "2026-10-22 09:14:03",
  "app_id": null,
  "app_name": null,
  "attempted_channels": [],
  "deliveries": [
    {
      "channel": "sms",
      "kind": "send",
      "status": "delivered",
      "simulated": false,
      "submitted_at": "2026-10-22 09:14:03",
      "delivered_at": "2026-10-22 09:14:07",
      "read_at": null,
      "failed_at": null,
      "error_code": null,
      "error_message": null
    }
  ],
  "callback_delivered": null,
  "cascade_triggered": false
}
DocsVerify/lookup/:number
GEThttps://api.envoisms.ma/v1/lookup/:number
Bearer

Identificar el operador de un número

Valida un número y devuelve su país y, para un móvil marroquí, el operador que lo atiende, portabilidad incluida. Se factura cada consulta correcta; un número inválido (400) no se factura.

Parámetros de consulta
ParámetroTipoEstadoDescripción
numberstringOpcionalAlternativa al parámetro de ruta: GET /v1/lookup?number=%2B212612345678.
Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Permiso de clave API requerido: send.
  • Errores: 400 MISSING_FIELD, 400 INVALID_PHONE, 402 INSUFFICIENT_BALANCE. Con una clave sandbox la respuesta es real pero no se cobra nada, y lleva "sandbox": true. type vale mobile o unknown; operator vale null si se desconoce.
GEThttps://api.envoisms.ma/v1/lookup/:number
curl -X GET "https://api.envoisms.ma/v1/lookup/{number}" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY"
{
  "id": "lkp_5d7f9b1c3e5a7c9e1b3d5f7a",
  "number": "+212612345678",
  "valid": true,
  "country_iso": "MA",
  "type": "mobile",
  "operator": "Maroc Telecom",
  "ported": false,
  "cost": { "eur": 0.005, "mad": 0.055 }
}
DocsVerify/hlr/:number
GEThttps://api.envoisms.ma/v1/hlr/:number
Bearer

Consultar la red (HLR)

Consulta el registro del operador (HLR): ¿el número es localizable ahora, qué red lo atiende hoy (portabilidad incluida), está en itinerancia, es móvil o fijo? Se factura cada respuesta de la red, sea cual sea; un número inválido (400) o una consulta no completada (502/503) no se factura.

Parámetros de consulta
ParámetroTipoEstadoDescripción
numberstringOpcionalAlternativa al parámetro de ruta: GET /v1/hlr?number=%2B212612345678.
Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Permiso de clave API requerido: send.
  • status vale active (localizable), absent (conocido pero apagado o sin cobertura), inactive (fuera de servicio), invalid o unknown. La respuesta tarda un viaje de ida y vuelta a la red (1 a 5 s). source indica de dónde viene la respuesta: network = el registro del operador consultado en directo (status refleja la localizabilidad); portability = la base de portabilidad de los operadores (números marroquíes): operador, portabilidad y línea en servicio (active), sin decir si el teléfono está encendido en ese instante. Errores: 400 MISSING_FIELD, 400 INVALID_PHONE, 402 INSUFFICIENT_BALANCE, 502 BAD_GATEWAY, 503 HLR_UNAVAILABLE. Con una clave sandbox la respuesta se simula a partir de lo que la plataforma ya sabe, no se consulta ni se cobra nada, y lleva "sandbox": true.
GEThttps://api.envoisms.ma/v1/hlr/:number
curl -X GET "https://api.envoisms.ma/v1/hlr/{number}" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY"
{
  "id": "hlr_5d7f9b1c3e5a7c9e1b3d5f7a",
  "number": "+212612345678",
  "valid": true,
  "status": "active",
  "type": "mobile",
  "country_iso": "MA",
  "operator": "Maroc Telecom",
  "mcc": "604",
  "mnc": "01",
  "ported": false,
  "ported_operator": null,
  "ported_mnc": null,
  "roaming": false,
  "roaming_country_iso": null,
  "roaming_operator": null,
  "roaming_mnc": null,
  "source": "portability",
  "cost": { "eur": 0.018, "mad": 0.20 }
}
DocsVerify/verify/lookup
POSThttps://api.envoisms.ma/v1/verify/lookup
Bearer

Validar el formato de un número

Valida el formato de un número y devuelve sus formas local e internacional, país, ubicación, operador de asignación y tipo de línea. No se factura.

Cuerpo de la petición (JSON)
CampoTipoEstadoDescripción
numberstringRequeridoNúmero, en formato local o internacional.
country_codestringOpcionalCódigo de país ISO de 2 letras (MA, FR…), recomendado para un número en formato local.
Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Permiso de clave API requerido: status.
  • carrier es el operador al que se asignó el rango, no necesariamente el que atiende un número portado: para el operador real, use GET /v1/lookup/:number. Errores: 400 MISSING_FIELD; 503 LOOKUP_UNAVAILABLE cuando el servicio no está disponible momentáneamente.
POSThttps://api.envoisms.ma/v1/verify/lookup
curl -X POST "https://api.envoisms.ma/v1/verify/lookup" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "number": "+212612345678"
  }'
{
  "valid": true,
  "number": "212612345678",
  "local_format": "0612345678",
  "international_format": "+212612345678",
  "country_prefix": "+212",
  "country_code": "MA",
  "country_name": "Morocco",
  "location": null,
  "carrier": "Maroc Telecom",
  "line_type": "mobile"
}
DocsConversations/conversations
GEThttps://api.envoisms.ma/v1/conversations
Bearer

Listar conversaciones

Devuelve los hilos de WhatsApp y SMS de la cuenta, con el estado de la ventana de 24 horas de cada uno y contadores globales.

Parámetros de consulta
ParámetroTipoEstadoDescripción
limitintegerOpcionalNúmero de resultados: 50 por defecto, 100 como máximo.
offsetintegerOpcionalNúmero de resultados que se omiten (paginación). Por defecto: 0.
filterstringOpcionalall (por defecto), unread, active_window o needs_human.
channelstringOpcionalall (por defecto), whatsapp o sms.
phone_number_id | assigned_to | searchstringOpcionalNúmero de WhatsApp conectado; agente asignado; búsqueda en el número, el nombre y el último mensaje.
Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Permiso de clave API requerido: send.
GEThttps://api.envoisms.ma/v1/conversations
curl -X GET "https://api.envoisms.ma/v1/conversations?limit=50" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY"
{
  "data": [
    {
      "id": "th_212612345678",
      "phone": "212612345678",
      "contact_name": "Yassine Alami",
      "channel": "whatsapp",
      "lead_status": "new",
      "unread_count": 1,
      "bot_muted": false,
      "handling_agent": null,
      "notes": null,
      "tags": [],
      "last_inbound_at": "2026-10-22 08:42:00",
      "last_outbound_at": "2026-10-21 17:10:00",
      "last_message_preview": "Bonjour, la veste existe en taille L ?",
      "last_message_role": "customer",
      "last_message_at": "2026-10-22 08:42:00",
      "is_window_active": true,
      "window_expires_at": "2026-10-23T08:42:00.000Z",
      "window_seconds_remaining": 84960,
      "waba_name": "Ma Boutique",
      "waba_phone": "+212522000000",
      "phone_number_id": "109283746501928"
    }
  ],
  "total": 1,
  "limit": 50,
  "offset": 0,
  "stats": {
    "total_threads": 1,
    "unread_threads": 1,
    "active_window_threads": 1,
    "human_takeover_threads": 0
  }
}
DocsConversations/conversations/:phone
GEThttps://api.envoisms.ma/v1/conversations/:phone
Bearer

Consultar una conversación

Devuelve un hilo y sus mensajes (500 como máximo), del más antiguo al más reciente, con el estado de la ventana de 24 horas.

Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Permiso de clave API requerido: send.
  • :phone acepta el número con o sin +. role vale customer para un mensaje recibido, agent o bot para uno enviado.
GEThttps://api.envoisms.ma/v1/conversations/:phone
curl -X GET "https://api.envoisms.ma/v1/conversations/{phone}" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY"
{
  "thread": {
    "id": "th_212612345678",
    "phone": "212612345678",
    "contact_name": "Yassine Alami",
    "channel": "whatsapp",
    "lead_status": "new",
    "unread_count": 0,
    "bot_muted": true,
    "handling_agent": "Salma",
    "notes": null,
    "tags": [],
    "last_inbound_at": "2026-10-22 08:42:00",
    "last_outbound_at": "2026-10-22 08:45:10",
    "is_window_active": true,
    "window_expires_at": "2026-10-23T08:42:00.000Z",
    "window_seconds_remaining": 84960,
    "window": {
      "is_window_active": true,
      "window_expires_at": "2026-10-23T08:42:00.000Z",
      "window_seconds_remaining": 84960
    },
    "waba_name": "Ma Boutique",
    "waba_phone": "+212522000000",
    "phone_number_id": "109283746501928"
  },
  "messages": [
    {
      "id": "wamsg_1c3e5a7c9e1b3d5f7a9c1e3b",
      "phone": "212612345678",
      "role": "customer",
      "message": "Bonjour, la veste existe en taille L ?",
      "wamid": "wamid.HBgLMjEyNjEyMzQ1Njc4FQIAEhgg",
      "status": "read",
      "media_type": "text",
      "media_url": null,
      "metadata": {},
      "read_at": "2026-10-22 08:43:00",
      "created_at": "2026-10-22 08:42:00"
    }
  ]
}
DocsConversations/conversations/:phone/messages
POSThttps://api.envoisms.ma/v1/conversations/:phone/messages
Bearer

Responder en una conversación

Envía una respuesta de agente (texto, medio o plantilla) y pausa el bot en ese hilo. El envío pasa por el mismo circuito que POST /v1/messages: se aplican la tarificación, la lista de bajas y el cobro. Responde 201.

Cuerpo de la petición (JSON)
CampoTipoEstadoDescripción
messagestringOpcionalTexto de la respuesta, o pie del medio. Obligatorio sin plantilla ni medio (400 MISSING_MESSAGE).
channelstringOpcional"whatsapp" (por defecto) o "sms".
template_id | template_name | template_language | variablesmixedOpcionalPlantilla de WhatsApp aprobada que se envía en lugar de texto libre (por identificador, o por nombre e idioma), con sus valores (array u objeto).
media_url | media_type | filenamestringOpcionalMedio adjunto: URL https (o data URI), tipo image o document, nombre de archivo del documento.
phone_number_idstringOpcionalNúmero de WhatsApp conectado que responde.
Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Permiso de clave API requerido: send.
  • Un mensaje libre de WhatsApp exige que el contacto haya escrito en las últimas 24 horas (si no, 400 OUT_OF_24H_WINDOW); una plantilla aprobada sale en cualquier momento. Otros errores: 403 WHATSAPP_NOT_CONNECTED, 404 TEMPLATE_NOT_FOUND, 400 TEMPLATE_NOT_APPROVED.
POSThttps://api.envoisms.ma/v1/conversations/:phone/messages
curl -X POST "https://api.envoisms.ma/v1/conversations/{phone}/messages" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "message": "Thanks, we will call you back today."
  }'
{
  "ok": true,
  "message": {
    "id": "msg_9e1b3d5f7a9c1e3b5d7f9a1c",
    "phone": "212612345678",
    "role": "agent",
    "message": "Oui, la taille L est disponible.",
    "wamid": "wamid.HBgLMjEyNjEyMzQ1Njc4FQIAERgS",
    "status": "sent",
    "channel": "whatsapp",
    "media_type": "text",
    "metadata": { "agent": "Salma" },
    "created_at": "2026-10-22T08:45:10.221Z"
  }
}
DocsLeads/qualified-leads
GEThttps://api.envoisms.ma/v1/qualified-leads
Bearer

Listar leads calificados

Devuelve los leads que registró su bot de WhatsApp, del más reciente al más antiguo, con su puntuación (0 a 100) y su estado. format=csv descarga la misma lista.

Parámetros de consulta
ParámetroTipoEstadoDescripción
limitintegerOpcionalNúmero de resultados: 100 por defecto, 500 como máximo.
statusstringOpcionalnew, contacted, qualified, Fournisseur, won o lost.
presetstringOpcionalSector del bot (por ejemplo medical_equipment); supplier agrupa las solicitudes de proveedores.
formatstringOpcionaljson (por defecto) o csv.
Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Cualquier clave API válida: no se exige ningún permiso concreto.
  • Sin paginación por offset: limit acota la lista. metrics se calcula sobre los leads devueltos; hot cuenta los que alcanzan 80.
GEThttps://api.envoisms.ma/v1/qualified-leads
curl -X GET "https://api.envoisms.ma/v1/qualified-leads?limit=50" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY"
{
  "ok": true,
  "leads": [
    {
      "id": "lead_3a5c7e9b1d3f5a7c9e1b3d5f",
      "account_id": "acct_7b2e4c6a8d0f1e3b5c7a9d2f",
      "phone_number_id": "109283746501928",
      "contact_phone": "+212612345678",
      "contact_name": "Cabinet Benjelloun",
      "preset": "medical_equipment",
      "category": "Échographe",
      "bant_score": 85,
      "urgency": "immediate",
      "budget": "200 000 - 300 000 MAD",
      "authority": "decision_maker",
      "notes": null,
      "source_type": "direct_inbound",
      "status": "new",
      "created_at": "2026-10-21 09:15:00",
      "updated_at": "2026-10-21 09:15:00"
    }
  ],
  "metrics": { "total": 1, "hot": 1, "average_score": 85 }
}
DocsLeads/qualified-leads/:id
PATCHhttps://api.envoisms.ma/v1/qualified-leads/:id
Bearer

Actualizar un lead

Cambia el estado, las notas o el nombre de un lead. Responde con el lead actualizado.

Cuerpo de la petición (JSON)
CampoTipoEstadoDescripción
statusstringOpcionalnew, contacted, qualified, Fournisseur, won o lost (si no, 400 INVALID_STATUS).
notesstringOpcionalNotas internas de seguimiento.
contact_namestringOpcionalNombre del contacto o del establecimiento.
value_minor | currencyinteger | stringOpcionalValor del negocio en céntimos y su divisa, transmitidos a Meta con un paso a won.
Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Cualquier clave API válida: no se exige ningún permiso concreto.
  • El paso a qualified o won se comunica a Meta (QualifiedLead, Purchase) cuando el lead llegó desde un anuncio que lleva a WhatsApp; solo ante un cambio real de estado. 404 LEAD_NOT_FOUND si el lead no pertenece a la cuenta.
PATCHhttps://api.envoisms.ma/v1/qualified-leads/:id
curl -X PATCH "https://api.envoisms.ma/v1/qualified-leads/{id}" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "contacted",
    "notes": "Called back on 22/10"
  }'
{
  "ok": true,
  "lead": {
    "id": "lead_3a5c7e9b1d3f5a7c9e1b3d5f",
    "account_id": "acct_7b2e4c6a8d0f1e3b5c7a9d2f",
    "phone_number_id": "109283746501928",
    "contact_phone": "+212612345678",
    "contact_name": "Cabinet Benjelloun",
    "preset": "medical_equipment",
    "category": "Échographe",
    "bant_score": 85,
    "urgency": "immediate",
    "budget": "200 000 - 300 000 MAD",
    "authority": "decision_maker",
    "notes": "Rendez-vous fixé jeudi.",
    "source_type": "direct_inbound",
    "status": "contacted",
    "created_at": "2026-10-21 09:15:00",
    "updated_at": "2026-10-22 10:02:31",
    "user_id": null
  }
}
DocsContacts/contacts
GEThttps://api.envoisms.ma/v1/contacts
Bearer

Listar contactos

Devuelve los contactos de la cuenta, con búsqueda y filtro por lista.

Parámetros de consulta
ParámetroTipoEstadoDescripción
limitintegerOpcionalNúmero de resultados: 100 por defecto, 500 como máximo.
offsetintegerOpcionalNúmero de resultados que se omiten (paginación). Por defecto: 0.
qstringOpcionalBúsqueda en nombre, teléfono y email.
list_idstringOpcionalSolo los miembros de esta lista (lst_…).
Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Permiso de clave API requerido: contacts.
GEThttps://api.envoisms.ma/v1/contacts
curl -X GET "https://api.envoisms.ma/v1/contacts?limit=50" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY"
{
  "data": [
    {
      "id": "ctc_2d4f6a8c0e1b3d5f7a9c1e3b",
      "account_id": "acct_7b2e4c6a8d0f1e3b5c7a9d2f",
      "phone": "+212612345678",
      "name": "Karim Bennani",
      "email": "karim@example.ma",
      "custom1": "VIP",
      "custom2": null,
      "custom3": null,
      "opted_out": 0,
      "opted_out_at": null,
      "created_at": "2026-10-10 14:20:00"
    }
  ],
  "limit": 100,
  "offset": 0,
  "total": 1
}
DocsContacts/contacts
POSThttps://api.envoisms.ma/v1/contacts
Bearer

Crear o actualizar un contacto

Crea un contacto, o actualiza el que ya tiene ese número (se sustituyen nombre, email y campos personalizados). Responde 201 con el contacto.

Cuerpo de la petición (JSON)
CampoTipoEstadoDescripción
phonestringRequeridoNúmero en formato E.164 (si no, 400 INVALID_PHONE).
name | emailstringOpcionalNombre y email del contacto.
list_idstringOpcionalAñade también el contacto a esta lista (404 LIST_NOT_FOUND si no es suya).
custom_fieldsobjectOpcional{"custom1", "custom2", "custom3"}: tres campos libres, utilizables en las campañas.
Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Permiso de clave API requerido: contacts.
POSThttps://api.envoisms.ma/v1/contacts
curl -X POST "https://api.envoisms.ma/v1/contacts" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "phone": "+212612345678",
    "name": "Karim Bennani",
    "email": "karim@example.ma"
  }'
{
  "id": "ctc_2d4f6a8c0e1b3d5f7a9c1e3b",
  "account_id": "acct_7b2e4c6a8d0f1e3b5c7a9d2f",
  "phone": "+212612345678",
  "name": "Karim Bennani",
  "email": "karim@example.ma",
  "custom1": "VIP",
  "custom2": null,
  "custom3": null,
  "opted_out": 0,
  "opted_out_at": null,
  "created_at": "2026-10-10 14:20:00"
}
DocsContacts/contacts/import
POSThttps://api.envoisms.ma/v1/contacts/import
Bearer

Importar contactos

Pone en cola una importación de contactos, en JSON o en archivo CSV, y responde 202 de inmediato; la importación se ejecuta después por lotes de 100.

Cuerpo de la petición (JSON)
CampoTipoEstadoDescripción
contactsarrayRequerido(application/json) Objetos {"phone", "name"?, "email"?, "custom1"?, "custom2"?, "custom3"?}. Para un CSV, envíe text/csv o multipart/form-data (campo file) y pase la lista en ?list_id=.
list_idstringOpcionalLista que recibe los contactos importados.
Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Permiso de clave API requerido: contacts.
  • La respuesta se da al poner en cola: total cuenta las filas recibidas e imported lo repite tal cual. Los números inválidos se descartan durante la importación; compruebe el resultado con GET /v1/contacts. Otros tipos de contenido: 415 UNSUPPORTED_MEDIA_TYPE.
POSThttps://api.envoisms.ma/v1/contacts/import
curl -X POST "https://api.envoisms.ma/v1/contacts/import" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "contacts": [
      {
        "phone": "+212611111111",
        "name": "Karim"
      },
      {
        "phone": "+212622222222",
        "name": "Salma"
      }
    ]
  }'
{
  "jobId": "job_4b6d8f0a2c4e6b8d0f2a4c6e",
  "total": 150,
  "imported": 150,
  "skipped": 0,
  "status": "queued"
}
DocsContacts/contacts/:id
DELETEhttps://api.envoisms.ma/v1/contacts/:id
Bearer

Eliminar un contacto

Elimina definitivamente un contacto. 404 NOT_FOUND si no existe.

Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Permiso de clave API requerido: contacts.
DELETEhttps://api.envoisms.ma/v1/contacts/:id
curl -X DELETE "https://api.envoisms.ma/v1/contacts/{id}" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY"
{
  "deleted": true,
  "id": "ctc_2d4f6a8c0e1b3d5f7a9c1e3b"
}
DocsContacts/contacts/lists
GEThttps://api.envoisms.ma/v1/contacts/lists
Bearer

Listar las listas de contactos

Devuelve las listas de contactos de la cuenta, con su número de miembros.

Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Permiso de clave API requerido: contacts.
GEThttps://api.envoisms.ma/v1/contacts/lists
curl -X GET "https://api.envoisms.ma/v1/contacts/lists" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY"
{
  "data": [
    {
      "id": "lst_f84b2d6a8c0e1f3b5d7a9c2e",
      "account_id": "acct_7b2e4c6a8d0f1e3b5c7a9d2f",
      "name": "Clients fidèles",
      "description": "Clients ayant commandé au moins deux fois",
      "count": 1420,
      "created_at": "2026-09-15 09:00:00"
    }
  ]
}
DocsContacts/contacts/lists
POSThttps://api.envoisms.ma/v1/contacts/lists
Bearer

Crear una lista

Crea una lista de contactos vacía, que se llena con POST /v1/contacts o /v1/contacts/import. Responde 201.

Cuerpo de la petición (JSON)
CampoTipoEstadoDescripción
namestringRequeridoNombre de la lista.
descriptionstringOpcionalDescripción libre.
Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Permiso de clave API requerido: contacts.
POSThttps://api.envoisms.ma/v1/contacts/lists
curl -X POST "https://api.envoisms.ma/v1/contacts/lists" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Newsletter",
    "description": "Customers who subscribed at checkout"
  }'
{
  "id": "lst_f84b2d6a8c0e1f3b5d7a9c2e",
  "name": "Clients fidèles",
  "description": "Clients ayant commandé au moins deux fois",
  "count": 0
}
DocsContacts/contacts/lists/:id
POSThttps://api.envoisms.ma/v1/contacts/lists/:id
Bearer

Renombrar una lista

Sustituye el nombre y la descripción de una lista. 404 NOT_FOUND si no existe.

Cuerpo de la petición (JSON)
CampoTipoEstadoDescripción
namestringRequeridoNuevo nombre.
descriptionstringOpcionalNueva descripción; si se omite, se borra.
Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Permiso de clave API requerido: contacts.
POSThttps://api.envoisms.ma/v1/contacts/lists/:id
curl -X POST "https://api.envoisms.ma/v1/contacts/lists/{id}" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY"
{
  "id": "lst_f84b2d6a8c0e1f3b5d7a9c2e",
  "name": "Clients fidèles 2026",
  "description": null
}
DocsContacts/contacts/lists/:id
DELETEhttps://api.envoisms.ma/v1/contacts/lists/:id
Bearer

Eliminar una lista

Elimina una lista y sus pertenencias; los contactos se conservan. Se rechaza (400 LIST_IN_USE) mientras una campaña programada, en curso o en pausa la use.

Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Permiso de clave API requerido: contacts.
DELETEhttps://api.envoisms.ma/v1/contacts/lists/:id
curl -X DELETE "https://api.envoisms.ma/v1/contacts/lists/{id}" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY"
{
  "deleted": true,
  "id": "lst_f84b2d6a8c0e1f3b5d7a9c2e"
}
DocsContacts/optouts
GEThttps://api.envoisms.ma/v1/optouts
Bearer

Listar las bajas

Devuelve los números dados de baja de sus envíos. No se les envía ningún mensaje (403 OPTED_OUT).

Parámetros de consulta
ParámetroTipoEstadoDescripción
limitintegerOpcionalNúmero de resultados: 100 por defecto, 500 como máximo.
offsetintegerOpcionalNúmero de resultados que se omiten (paginación). Por defecto: 0.
Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Permiso de clave API requerido: optouts.
GEThttps://api.envoisms.ma/v1/optouts
curl -X GET "https://api.envoisms.ma/v1/optouts?limit=50" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY"
{
  "data": [
    { "phone": "+212611111111", "opted_out_at": "2026-10-01 12:00:00" }
  ],
  "limit": 100,
  "offset": 0,
  "total": 1
}
DocsContacts/optouts
POSThttps://api.envoisms.ma/v1/optouts
Bearer

Añadir una baja

Añade un número a su lista de bajas. Responde 201.

Cuerpo de la petición (JSON)
CampoTipoEstadoDescripción
phonestringRequeridoNúmero en formato E.164.
Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Permiso de clave API requerido: optouts.
POSThttps://api.envoisms.ma/v1/optouts
curl -X POST "https://api.envoisms.ma/v1/optouts" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "phone": "+212612345678"
  }'
{
  "phone": "+212611111111",
  "opted_out": true
}
DocsContacts/optouts/:phone
DELETEhttps://api.envoisms.ma/v1/optouts/:phone
Bearer

Retirar una baja

Retira un número de la lista de bajas. Hágalo solo con un nuevo consentimiento de la persona, que debe registrarse con POST /v1/consents.

Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Permiso de clave API requerido: optouts.
DELETEhttps://api.envoisms.ma/v1/optouts/:phone
curl -X DELETE "https://api.envoisms.ma/v1/optouts/{phone}" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY"
{
  "phone": "+212611111111",
  "opted_out": false
}
DocsContacts/consents
GEThttps://api.envoisms.ma/v1/consents
Bearer

Listar consentimientos

Registro de consentimientos de solo adición (ley 09-08): cada consentimiento y cada retirada es un evento fechado, con su fuente y su prueba. La plataforma registra por sí misma las palabras clave STOP/START recibidas y la primera conversación que abre el cliente.

Parámetros de consulta
ParámetroTipoEstadoDescripción
limitintegerOpcionalNúmero de resultados: 100 por defecto, 500 como máximo.
offsetintegerOpcionalNúmero de resultados que se omiten (paginación). Por defecto: 0.
phonestringOpcionalNúmero E.164.
purposestringOpcionalmarketing, transactional, otp o service.
statusstringOpcionalgranted o withdrawn.
from | tostringOpcionalLímites ISO 8601 sobre recorded_at.
formatstringOpcionalcsv: descarga todo el conjunto filtrado.
Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Permiso de clave API requerido: contacts.
GEThttps://api.envoisms.ma/v1/consents
curl -X GET "https://api.envoisms.ma/v1/consents?limit=50" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY"
{
  "data": [
    {
      "id": "cns_9f2a4c6e8b0d1f3a5c7e9b2d",
      "phone": "+212612345678",
      "channel": "whatsapp",
      "purpose": "marketing",
      "status": "withdrawn",
      "source": "whatsapp_keyword",
      "evidence": { "keyword": "stop", "lang": "fr" },
      "recorded_at": "2026-10-05T09:00:00.000Z",
      "created_at": "2026-10-05T09:00:00.000Z"
    }
  ],
  "limit": 100,
  "offset": 0,
  "total": 1
}
DocsContacts/consents
POSThttps://api.envoisms.ma/v1/consents
Bearer

Registrar un consentimiento

Registra que una persona dio o retiró su consentimiento, con la prueba que usted tiene. Una retirada de marketing pone el número en la lista de bajas de inmediato. Responde 201.

Cuerpo de la petición (JSON)
CampoTipoEstadoDescripción
phonestringRequeridoNúmero en formato E.164.
purposestringRequeridomarketing, transactional, otp o service.
statusstringOpcionalgranted (por defecto) o withdrawn.
channelstringOpcionalwhatsapp, sms o any (por defecto).
sourcestringOpcionalapi (por defecto), form, import o dashboard. Las fuentes por palabra clave están reservadas a la plataforma.
evidenceobjectOpcionalObjeto JSON libre de menos de 4 096 bytes: texto mostrado, URL, IP, referencia.
recorded_atstringOpcionalFecha ISO 8601 del acto (por defecto: ahora); puede antedatarse para una importación, nunca en el futuro.
Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Permiso de clave API requerido: contacts.
  • Errores: 400 INVALID_PHONE, INVALID_PURPOSE, INVALID_STATUS, INVALID_CHANNEL, INVALID_SOURCE, INVALID_EVIDENCE, INVALID_DATE.
POSThttps://api.envoisms.ma/v1/consents
curl -X POST "https://api.envoisms.ma/v1/consents" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "phone": "+212612345678",
    "purpose": "marketing",
    "status": "granted",
    "channel": "whatsapp",
    "source": "form",
    "evidence": {
      "form": "checkout",
      "ip": "196.200.1.4"
    }
  }'
{
  "id": "cns_1b3d5f7a9c2e4b6d8f0a2c4e",
  "phone": "+212612345678",
  "channel": "any",
  "purpose": "marketing",
  "status": "granted",
  "source": "form",
  "evidence": { "text": "J’accepte de recevoir les offres par WhatsApp", "url": "https://example.ma/inscription" },
  "recorded_at": "2026-10-01T10:00:00.000Z",
  "created_at": "2026-10-01T10:00:00.000Z"
}
DocsContacts/consents/:phone
GEThttps://api.envoisms.ma/v1/consents/:phone
Bearer

Estado e historial de un número

Devuelve el estado actual por finalidad (del evento más reciente; unknown sin historial, nunca presumido), la presencia en la lista de bajas y el historial (500 eventos como máximo).

Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Permiso de clave API requerido: contacts.
GEThttps://api.envoisms.ma/v1/consents/:phone
curl -X GET "https://api.envoisms.ma/v1/consents/{phone}" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY"
{
  "phone": "+212612345678",
  "opted_out": true,
  "opted_out_at": "2026-10-05T09:00:00.000Z",
  "state": {
    "marketing": { "status": "withdrawn", "channel": "whatsapp", "source": "whatsapp_keyword", "recorded_at": "2026-10-05T09:00:00.000Z", "record_id": "cns_9f2a4c6e8b0d1f3a5c7e9b2d" },
    "transactional": { "status": "unknown" },
    "otp": { "status": "unknown" },
    "service": { "status": "granted", "channel": "whatsapp", "source": "whatsapp_inbound", "recorded_at": "2026-07-01T09:00:00.000Z", "record_id": "cns_0a2c4e6b8d1f3a5c7e9b0d2f" }
  },
  "history": []
}
DocsCampaigns/campaigns
GEThttps://api.envoisms.ma/v1/campaigns
Bearer

Listar campañas

Devuelve las campañas de la cuenta, de la más reciente a la más antigua, con sus contadores.

Parámetros de consulta
ParámetroTipoEstadoDescripción
limitintegerOpcionalNúmero de resultados: 50 por defecto, 200 como máximo.
offsetintegerOpcionalNúmero de resultados que se omiten (paginación). Por defecto: 0.
Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Permiso de clave API requerido: campaigns.
  • Estados: draft, scheduled, running, paused, completed, failed, cancelled.
GEThttps://api.envoisms.ma/v1/campaigns
curl -X GET "https://api.envoisms.ma/v1/campaigns?limit=50" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY"
{
  "data": [
    {
      "id": "cmp_8d2a4c6e1b3f5a7c9e0b2d4f",
      "account_id": "acct_7b2e4c6a8d0f1e3b5c7a9d2f",
      "name": "Soldes d’automne",
      "channel": "sms",
      "list_id": "lst_f84b2d6a8c0e1f3b5d7a9c2e",
      "template_id": null,
      "body": "Bonjour {{name}}, -30% sur la collection avec le code AUT30.",
      "sender_id": "MABOUTIQUE",
      "status": "completed",
      "scheduled_at": null,
      "started_at": "2026-10-15 10:00:00",
      "completed_at": "2026-10-15 10:06:12",
      "total_count": 1420,
      "sent_count": 1412,
      "delivered_count": 1380,
      "failed_count": 8,
      "cost_eur": 102.24,
      "cost_mad": 1124.64,
      "created_at": "2026-10-14 16:30:00",
      "buttons": null,
      "template_variables": null
    }
  ],
  "total": 1,
  "limit": 50,
  "offset": 0
}
DocsCampaigns/campaigns
POSThttps://api.envoisms.ma/v1/campaigns
Bearer

Crear una campaña

Crea una campaña en borrador, o programada si se indica scheduled_at. No sale nada antes de POST /v1/campaigns/:id/send. Responde 201.

Cuerpo de la petición (JSON)
CampoTipoEstadoDescripción
namestringRequeridoNombre de la campaña.
bodystringOpcional(SMS) Texto del mensaje. Se exige body o template_id.
template_idstringOpcionalPlantilla de la cuenta. Obligatoria en WhatsApp, donde Meta debe haberla aprobado (400 TEMPLATE_NOT_APPROVED).
channelstringOpcional"sms" (por defecto) o "whatsapp" (exige un número de WhatsApp Business conectado, si no 403 WHATSAPP_NOT_CONNECTED).
list_idstringOpcionalLista destinataria; necesaria para lanzar la campaña (404 LIST_NOT_FOUND si no es suya).
sender_idstringOpcionalSMS: Sender ID (mismas reglas que POST /v1/messages; por defecto EnvoiSMS). WhatsApp: número conectado que envía (por defecto, su número predeterminado; 400 INVALID_WABA_SENDER si no es suyo).
scheduled_atstringOpcionalFecha ISO 8601; pone la campaña en estado scheduled.
template_variablesobjectOpcional(WhatsApp) Valor de cada parámetro de la plantilla, por clave. Clave "1", "2"… o nombre de variable: un campo del contacto (name, phone, email, custom1, custom2, custom3) o un texto fijo con el prefijo "static:". "_header_media_url": enlace https del medio del encabezado (obligatorio para un encabezado de imagen, vídeo o documento). "_button_<i>": parte variable del botón de enlace de índice i (desde 0). Sin valor, {{1}} lee name, {{2}} custom1 y {{3}} custom2.
consent_attestedbooleanOpcional(WhatsApp, plantilla marketing) true: usted declara que cada contacto de la lista aceptó recibir sus mensajes de WhatsApp. Obligatorio para una plantilla marketing (si no, 400 CONSENT_ATTESTATION_REQUIRED); la declaración queda fechada.
buttonsarrayOpcional(WhatsApp) Botones, mismo formato que POST /v1/messages. Rechazados en SMS (400 BUTTONS_NOT_SUPPORTED).
Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Permiso de clave API requerido: campaigns.
  • WhatsApp: todo lo que la plantilla exige por contacto se comprueba al crear; lo que falta se nombra en 400 MISSING_TEMPLATE_PARAMS. Otros errores: 400 MISSING_TEMPLATE, 404 TEMPLATE_NOT_FOUND.
POSThttps://api.envoisms.ma/v1/campaigns
curl -X POST "https://api.envoisms.ma/v1/campaigns" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Summer sale",
    "channel": "sms",
    "body": "Summer sale: -20% until Sunday. Reply STOP to unsubscribe.",
    "list_id": "lst_…"
  }'
{
  "id": "cmp_8d2a4c6e1b3f5a7c9e0b2d4f",
  "status": "draft"
}
DocsCampaigns/campaigns/:id
GEThttps://api.envoisms.ma/v1/campaigns/:id
Bearer

Consultar una campaña

Devuelve una campaña, su estado y sus contadores de envío. Si no, 404 NOT_FOUND.

Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Permiso de clave API requerido: campaigns.
GEThttps://api.envoisms.ma/v1/campaigns/:id
curl -X GET "https://api.envoisms.ma/v1/campaigns/{id}" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY"
{
  "id": "cmp_8d2a4c6e1b3f5a7c9e0b2d4f",
  "account_id": "acct_7b2e4c6a8d0f1e3b5c7a9d2f",
  "name": "Nouvelle collection",
  "channel": "whatsapp",
  "list_id": "lst_f84b2d6a8c0e1f3b5d7a9c2e",
  "template_id": "tpl_8e2a4c6b1d3f5a7c9e0b2d4f",
  "body": null,
  "sender_id": "+212522000000",
  "status": "running",
  "scheduled_at": null,
  "started_at": "2026-10-22 10:00:00",
  "completed_at": null,
  "total_count": 1420,
  "sent_count": 840,
  "delivered_count": 790,
  "failed_count": 3,
  "cost_eur": 0,
  "cost_mad": 0,
  "created_at": "2026-10-21 18:12:40",
  "buttons": null,
  "template_variables": "{\"1\":\"name\"}"
}
DocsCampaigns/campaigns/:id
PATCHhttps://api.envoisms.ma/v1/campaigns/:id
Bearer

Modificar una campaña

Modifica una campaña en borrador o programada; solo cambian los campos enviados. Responde con la campaña actualizada.

Cuerpo de la petición (JSON)
CampoTipoEstadoDescripción
name | body | sender_id | scheduled_atstringOpcionalNuevo nombre, texto, remitente o fecha programada (ISO 8601). Un valor vacío deja el campo sin cambios.
Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Permiso de clave API requerido: campaigns.
  • 409 CAMPAIGN_NOT_EDITABLE si la campaña no está en draft ni en scheduled; 404 NOT_FOUND si no existe.
PATCHhttps://api.envoisms.ma/v1/campaigns/:id
curl -X PATCH "https://api.envoisms.ma/v1/campaigns/{id}" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "scheduled_at": "2026-11-01T09:00:00Z"
  }'
{
  "id": "cmp_8d2a4c6e1b3f5a7c9e0b2d4f",
  "account_id": "acct_7b2e4c6a8d0f1e3b5c7a9d2f",
  "name": "Soldes d’automne — relance",
  "channel": "sms",
  "list_id": "lst_f84b2d6a8c0e1f3b5d7a9c2e",
  "template_id": null,
  "body": "Dernier jour : -30% avec le code AUT30.",
  "sender_id": "MABOUTIQUE",
  "status": "scheduled",
  "scheduled_at": "2026-10-25T09:00:00Z",
  "started_at": null,
  "completed_at": null,
  "total_count": 0,
  "sent_count": 0,
  "delivered_count": 0,
  "failed_count": 0,
  "cost_eur": 0,
  "cost_mad": 0,
  "created_at": "2026-10-22 11:05:00",
  "buttons": null,
  "template_variables": null
}
DocsCampaigns/campaigns/:id
DELETEhttps://api.envoisms.ma/v1/campaigns/:id
Bearer

Cancelar una campaña

Pasa la campaña a cancelled; sigue siendo consultable. Una campaña en curso debe pausarse antes (409 CAMPAIGN_RUNNING).

Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Permiso de clave API requerido: campaigns.
DELETEhttps://api.envoisms.ma/v1/campaigns/:id
curl -X DELETE "https://api.envoisms.ma/v1/campaigns/{id}" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY"
{
  "deleted": true,
  "id": "cmp_8d2a4c6e1b3f5a7c9e0b2d4f"
}
DocsCampaigns/campaigns/:id/send
POSThttps://api.envoisms.ma/v1/campaigns/:id/send
Bearer

Lanzar una campaña

Lanza la campaña a los miembros de su lista que no se han dado de baja. El envío se hace en segundo plano; sígalo con GET /v1/campaigns/:id. Responde 202.

Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Permiso de clave API requerido: campaigns.
  • Errores: 400 ALREADY_RUNNING, 400 MISSING_LIST, 400 EMPTY_LIST (ningún destinatario elegible), 400 MISSING_BODY, 404 NOT_FOUND. Cada mensaje se tarifica y se cobra como un envío individual.
POSThttps://api.envoisms.ma/v1/campaigns/:id/send
curl -X POST "https://api.envoisms.ma/v1/campaigns/{id}/send" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY"
{
  "id": "cmp_8d2a4c6e1b3f5a7c9e0b2d4f",
  "status": "running",
  "total": 1420
}
DocsCampaigns/campaigns/:id/pause
POSThttps://api.envoisms.ma/v1/campaigns/:id/pause
Bearer

Pausar una campaña

Pausa una campaña en curso. 409 NOT_RUNNING si no lo está.

Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Permiso de clave API requerido: campaigns.
POSThttps://api.envoisms.ma/v1/campaigns/:id/pause
curl -X POST "https://api.envoisms.ma/v1/campaigns/{id}/pause" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY"
{
  "paused": true,
  "id": "cmp_8d2a4c6e1b3f5a7c9e0b2d4f"
}
DocsCampaigns/campaigns/:id/resume
POSThttps://api.envoisms.ma/v1/campaigns/:id/resume
Bearer

Reanudar una campaña

Reanuda una campaña en pausa donde se detuvo. 409 NOT_PAUSED si no está en pausa.

Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Permiso de clave API requerido: campaigns.
POSThttps://api.envoisms.ma/v1/campaigns/:id/resume
curl -X POST "https://api.envoisms.ma/v1/campaigns/{id}/resume" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY"
{
  "resumed": true,
  "id": "cmp_8d2a4c6e1b3f5a7c9e0b2d4f"
}
DocsWebhooks/webhooks
GEThttps://api.envoisms.ma/v1/webhooks
Bearer

Listar webhooks

Devuelve sus webhooks activos. El secreto solo aparece enmascarado (secret_hint); se muestra completo únicamente al crear y al rotar.

Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Permiso de clave API requerido: webhooks.
GEThttps://api.envoisms.ma/v1/webhooks
curl -X GET "https://api.envoisms.ma/v1/webhooks" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY"
{
  "data": [
    {
      "id": "whk_5f2b7d9a1c3e5b7d9f1a3c5e",
      "name": "Webhook Endpoint",
      "target_type": "custom",
      "format": "standard",
      "url": "https://example.ma/hooks/envoisms",
      "events": "[\"message.delivered\",\"message.failed\"]",
      "active": 1,
      "last_triggered_at": "2026-10-22 09:14:09",
      "last_status": 200,
      "created_at": "2026-10-01 10:00:00",
      "secret_hint": "••••9c2e"
    }
  ]
}
DocsWebhooks/webhooks
POSThttps://api.envoisms.ma/v1/webhooks
Bearer

Crear un webhook

Registra una URL HTTPS que recibirá los eventos elegidos. Responde 201 con el secreto de firma, mostrado solo esta vez.

Cuerpo de la petición (JSON)
CampoTipoEstadoDescripción
urlstringRequeridoURL HTTPS pública (si no, 400 INVALID_URL).
eventsstring[]Opcionalmessage.sent, message.delivered, message.read, message.failed, message.undeliverable, message.fallback, message.inbound, message.reaction, contact.optout, contact.optin, lead.qualified; comodines "message.*" y "*". Por defecto: ["message.delivered", "message.failed"].
secretstringOpcionalSecreto de firma; se genera (sec_…) si se omite.
name | formatstringOpcionalEtiqueta; formato standard ({"event", "data", "timestamp"}) o flat (campos de data también en la raíz, para una hoja de cálculo).
Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Permiso de clave API requerido: webhooks.
  • Firma: cada envío lleva X-EnvoiSMS-Signature-256: t=<hora unix>,v1=<HMAC-SHA256 hexadecimal de "t.cuerpo" con el secreto>, y X-EnvoiSMS-Event. Rechace una marca de tiempo de más de 5 minutos. Un fallo (5xx, 429 o red) se reintenta tras 60 s, 10 min y luego 1 h: 4 intentos en total.
  • El número de webhooks activos depende del plan (Starter 2, Business 5, Pro 15, Enterprise 50); por encima, 403 LIMIT_EXCEEDED.
POSThttps://api.envoisms.ma/v1/webhooks
curl -X POST "https://api.envoisms.ma/v1/webhooks" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.ma/webhooks/envoisms",
    "events": [
      "message.delivered",
      "message.failed",
      "message.inbound"
    ]
  }'
{
  "id": "whk_5f2b7d9a1c3e5b7d9f1a3c5e",
  "name": "Webhook Endpoint",
  "target_type": "custom",
  "format": "standard",
  "url": "https://example.ma/hooks/envoisms",
  "events": ["message.delivered", "message.failed"],
  "secret": "sec_3d5f7a9c1e3b5d7f9a1c9c2e",
  "active": true
}
DocsWebhooks/webhooks/:id
PATCHhttps://api.envoisms.ma/v1/webhooks/:id
Bearer

Modificar un webhook

Modifica la URL, los eventos, el nombre, el formato o el estado activo de un webhook; solo cambian los campos enviados.

Cuerpo de la petición (JSON)
CampoTipoEstadoDescripción
url | events | name | formatmixedOpcionalMismas reglas que al crear.
activebooleanOpcionalfalse detiene los envíos a este webhook.
Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Permiso de clave API requerido: webhooks.
PATCHhttps://api.envoisms.ma/v1/webhooks/:id
curl -X PATCH "https://api.envoisms.ma/v1/webhooks/{id}" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "active": false
  }'
{
  "updated": true,
  "id": "whk_5f2b7d9a1c3e5b7d9f1a3c5e"
}
DocsWebhooks/webhooks/:id
DELETEhttps://api.envoisms.ma/v1/webhooks/:id
Bearer

Eliminar un webhook

Desactiva el webhook: no se le envía ningún evento más y desaparece de la lista.

Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Permiso de clave API requerido: webhooks.
DELETEhttps://api.envoisms.ma/v1/webhooks/:id
curl -X DELETE "https://api.envoisms.ma/v1/webhooks/{id}" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY"
{
  "deleted": true,
  "id": "whk_5f2b7d9a1c3e5b7d9f1a3c5e"
}
DocsWebhooks/webhooks/:id/test
POSThttps://api.envoisms.ma/v1/webhooks/:id/test
Bearer

Probar un webhook

Envía un evento de ejemplo, firmado como uno real, a la URL del webhook. Responde 202 con el ejemplo enviado.

Cuerpo de la petición (JSON)
CampoTipoEstadoDescripción
eventstringOpcionalNombre del evento que se simula (por defecto: lead.qualified). Los datos de ejemplo son un mensaje recibido para message.inbound y un lead calificado en otro caso.
Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Permiso de clave API requerido: webhooks.
POSThttps://api.envoisms.ma/v1/webhooks/:id/test
curl -X POST "https://api.envoisms.ma/v1/webhooks/{id}/test" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "event": "message.delivered"
  }'
{
  "queued": true,
  "id": "whk_5f2b7d9a1c3e5b7d9f1a3c5e",
  "event": "message.inbound",
  "sample": {
    "event": "message.inbound",
    "data": {
      "message_id": "msg_demo_k3x9f2ab",
      "from": "+212612345678",
      "to": "+212708789095",
      "channel": "whatsapp",
      "text": "Bonjour, je souhaite des informations sur vos tarifs et solutions.",
      "contact_name": "Karim Bennani",
      "timestamp": "2026-10-22T09:30:00.000Z"
    },
    "timestamp": "2026-10-22T09:30:00.000Z"
  }
}
DocsWebhooks/webhooks/:id/rotate-secret
POSThttps://api.envoisms.ma/v1/webhooks/:id/rotate-secret
Bearer

Rotar el secreto de un webhook

Sustituye el secreto de firma y devuelve el nuevo, solo esta vez. Los envíos ya en cola siguen firmados con el anterior: acepte ambos durante unos minutos.

Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Permiso de clave API requerido: webhooks.
POSThttps://api.envoisms.ma/v1/webhooks/:id/rotate-secret
curl -X POST "https://api.envoisms.ma/v1/webhooks/{id}/rotate-secret" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY"
{
  "id": "whk_5f2b7d9a1c3e5b7d9f1a3c5e",
  "secret": "sec_7a9c1e3b5d7f9a1c3e5b0d2f",
  "rotated": true
}
DocsWebhooks/webhooks/logs
GEThttps://api.envoisms.ma/v1/webhooks/logs
Bearer

Registro de envíos de webhook

Devuelve los 100 últimos intentos de envío, del más reciente al más antiguo, con el cuerpo enviado y la respuesta de su servidor.

Parámetros de consulta
ParámetroTipoEstadoDescripción
webhook_idstringOpcionalSolo los envíos de este webhook.
Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Permiso de clave API requerido: webhooks.
GEThttps://api.envoisms.ma/v1/webhooks/logs
curl -X GET "https://api.envoisms.ma/v1/webhooks/logs" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY"
{
  "data": [
    {
      "id": "0b6f3c2e-8a4d-4f1b-9c7e-2d5a1f8e6b30",
      "webhook_id": "whk_5f2b7d9a1c3e5b7d9f1a3c5e",
      "event": "message.delivered",
      "url": "https://example.ma/hooks/envoisms",
      "payload": "{\"event\":\"message.delivered\",\"data\":{\"id\":\"msg_3f9a1c7e5b2d4a6f8e0c1b3d\",\"to\":\"+212612345678\",\"channel\":\"sms\",\"status\":\"delivered\"},\"timestamp\":\"2026-10-22T09:14:09.120Z\"}",
      "status_code": 500,
      "error_message": null,
      "attempt": 1,
      "delivered": 0,
      "created_at": "2026-10-22 09:14:09"
    }
  ],
  "max_attempts": 4
}
DocsWebhooks/webhooks/logs/:log_id/replay
POSThttps://api.envoisms.ma/v1/webhooks/logs/:log_id/replay
Bearer

Repetir un envío de webhook

Reenvía un envío registrado — mismo evento, mismos datos, firma nueva — a la URL actual del webhook, con los reintentos habituales. Responde 202.

Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Permiso de clave API requerido: webhooks.
  • 404 NOT_FOUND si el registro es desconocido o su webhook se eliminó; 422 REPLAY_UNAVAILABLE si no se conservó el cuerpo enviado.
POSThttps://api.envoisms.ma/v1/webhooks/logs/:log_id/replay
curl -X POST "https://api.envoisms.ma/v1/webhooks/logs/{log_id}/replay" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY"
{
  "queued": true,
  "replay_of": "0b6f3c2e-8a4d-4f1b-9c7e-2d5a1f8e6b30",
  "webhook_id": "whk_5f2b7d9a1c3e5b7d9f1a3c5e",
  "event": "message.delivered"
}
DocsWebhooks/inbound-webhooks
GEThttps://api.envoisms.ma/v1/inbound-webhooks
Bearer

Listar webhooks entrantes

Devuelve sus webhooks entrantes: URL de captura que convierten una llamada de una herramienta externa (formulario, Zapier, Make…) en un mensaje enviado.

Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Cualquier clave API válida: no se exige ningún permiso concreto.
GEThttps://api.envoisms.ma/v1/inbound-webhooks
curl -X GET "https://api.envoisms.ma/v1/inbound-webhooks" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY"
{
  "data": [
    {
      "id": "inwh_9a8b7c6d5e4f3a2b1c0d9e8f",
      "name": "Formulaire de contact",
      "channel": "whatsapp",
      "template_id": "tpl_4c1e9a7b3d5f2e8a6c0b1d3f",
      "active": 1,
      "created_at": "2026-10-12 14:00:00"
    }
  ]
}
DocsWebhooks/inbound-webhooks
POSThttps://api.envoisms.ma/v1/inbound-webhooks
Bearer

Crear un webhook entrante

Crea una URL de captura, POST /v1/inbound-webhooks/catch/:id: cada llamada JSON que recibe envía un mensaje al número leído en el cuerpo. Responde 201.

Cuerpo de la petición (JSON)
CampoTipoEstadoDescripción
namestringRequeridoEtiqueta de la fuente.
channelstringRequeridosms o whatsapp.
mapping_configobjectRequerido{"to_path": "contact.phone", "variables": {"1": "contact.name"}}: ruta del número en el JSON recibido y ruta de cada variable de la plantilla.
template_idstringOpcionalPlantilla que se envía (identificador o nombre).
Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Cualquier clave API válida: no se exige ningún permiso concreto.
  • La URL de captura no pide clave: su identificador hace de secreto, no lo publique. Acepta 64 KB como máximo y 120 llamadas por minuto, responde 202 {"success": true, "messageId"} y cobra su saldo como un envío normal. Para eliminarla: DELETE /v1/inbound-webhooks/:id.
POSThttps://api.envoisms.ma/v1/inbound-webhooks
curl -X POST "https://api.envoisms.ma/v1/inbound-webhooks" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Website form",
    "channel": "whatsapp",
    "mapping_config": {
      "to_path": "phone"
    }
  }'
{
  "id": "inwh_9a8b7c6d5e4f3a2b1c0d9e8f"
}
DocsBilling/billing/balance
GEThttps://api.envoisms.ma/v1/billing/balance
Bearer

Consultar el saldo

Devuelve el saldo prepago en dírhams y euros, el plan y su fecha de vencimiento.

Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Permiso de clave API requerido: balance.
  • GET /v1/balance devuelve los cuatro primeros campos con el mismo permiso.
GEThttps://api.envoisms.ma/v1/billing/balance
curl -X GET "https://api.envoisms.ma/v1/billing/balance" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY"
{
  "balance_mad": 1492.5,
  "balance_eur": 135.68,
  "primary_currency": "MAD",
  "plan": "business",
  "plan_expires_at": "2026-12-31 23:59:59",
  "plan_days_left": 70
}
DocsBilling/billing/transactions
GEThttps://api.envoisms.ma/v1/billing/transactions
Bearer

Listar transacciones

Devuelve los movimientos del saldo, del más reciente al más antiguo: recargas, consumo, reembolsos, ajustes y créditos regalados, cada uno con el saldo posterior.

Parámetros de consulta
ParámetroTipoEstadoDescripción
limitintegerOpcionalNúmero de resultados: 50 por defecto, 200 como máximo.
offsetintegerOpcionalNúmero de resultados que se omiten (paginación). Por defecto: 0.
typestringOpcionaltopup, usage, refund, adjustment o grant (si no, 400 INVALID_FIELD).
Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Permiso de clave API requerido: billing.
GEThttps://api.envoisms.ma/v1/billing/transactions
curl -X GET "https://api.envoisms.ma/v1/billing/transactions?limit=50" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY"
{
  "data": [
    {
      "id": "tx_6e8a0c2e4b6d8f0a2c4e6b8d",
      "type": "usage",
      "status": "completed",
      "amount_eur": -0.072,
      "amount_mad": -0.792,
      "balance_after_eur": 135.68,
      "balance_after_mad": 1492.5,
      "payment_method": null,
      "description": "SMS +212612345678",
      "reference": "msg_3f9a1c7e5b2d4a6f8e0c1b3d",
      "stripe_invoice_id": null,
      "created_at": "2026-10-22 09:14:03"
    }
  ],
  "limit": 50,
  "offset": 0,
  "total": 1,
  "type": null
}
DocsMachine Payments/machine-payments/status
GEThttps://api.envoisms.ma/v1/machine-payments/status
Public

Estado de los pagos máquina

Indica si un agente sin cuenta puede pagar el acceso a la API con un depósito en USDC (HTTP 402, protocolo MPP), y en qué condiciones. Sin autenticación.

Cabeceras
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Si está desactivado, la respuesta es {"enabled": false}.
  • Recorrido: llame a /v1/messages, /v1/waba, /v1/verify o /v1/mcp sin cabecera Authorization y con X-Payment-Method: mpp (o Accept: application/problem+json); la respuesta es 402 con una cabecera WWW-Authenticate: Payment que describe el depósito. Deposite deposit_usd en USDC y repita la llamada con Authorization: Payment <credential>. La respuesta lleva la clave API asignada en X-Machine-Session-Key (una sola vez) y el recibo en Payment-Receipt; use después esa clave como Bearer.
  • Sin X-Payment-Method ni Accept: application/problem+json, una llamada no autenticada recibe 401 UNAUTHORIZED. Un depósito aún no confirmado da 402 PAYMENT_NOT_SETTLED; una credencial ilegible, 400 INVALID_CREDENTIAL. La clave asignada solo tiene los permisos send, status, balance y verify. Como máximo 5 desafíos por minuto y por IP.
GEThttps://api.envoisms.ma/v1/machine-payments/status
curl -X GET "https://api.envoisms.ma/v1/machine-payments/status" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY"
{
  "enabled": true,
  "deposit_usd": 5,
  "credit_mad": 46.5,
  "networks": ["base", "tempo"],
  "permissions": ["send", "status", "balance", "verify"]
}
DocsAnalytics/analytics
GEThttps://api.envoisms.ma/v1/analytics
Bearer

Estadísticas de envío

Devuelve, sobre una ventana de días, los volúmenes, la entrega confirmada, el gasto cobrado y su desglose por canal, operador, estado y día.

Parámetros de consulta
ParámetroTipoEstadoDescripción
daysintegerOpcionalTamaño de la ventana, 30 por defecto, 365 como máximo.
campaign_idstringOpcionalEstadísticas de una sola campaña (la respuesta omite entonces error_diagnostics y waba_breakdown).
Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Permiso de clave API requerido: analytics.
  • delivery_rate se calcula solo sobre los mensajes cuya entrega se confirmó o negó; coverage_pct indica qué parte de los mensajes recibió esa confirmación. Un valor desconocido es null, nunca 100. cost_* es lo cobrado, descontados los reembolsos. La respuesta se guarda en caché 60 segundos (cabecera X-Cache).
GEThttps://api.envoisms.ma/v1/analytics
curl -X GET "https://api.envoisms.ma/v1/analytics" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY"
{
  "window_days": 30,
  "campaign_id": null,
  "summary": {
    "total": 420,
    "delivered": 398,
    "failed": 6,
    "unconfirmed": 16,
    "delivery_rate": 98.51,
    "coverage_pct": 96.19,
    "cost_eur": 30.24,
    "cost_mad": 332.64,
    "avg_latency": 4.2
  },
  "channels": [
    { "channel": "sms", "total": 420, "cost_eur": 30.24, "cost_mad": 332.64, "fallback_sends": 0 }
  ],
  "operators": [
    { "operator": "Maroc Telecom", "total": 250, "delivered": 240, "failed": 2, "unconfirmed": 8, "cost_eur": 18, "cost_mad": 198, "delivery_rate": 99.17, "coverage_pct": 96.8, "avg_latency": null }
  ],
  "status_distribution": [
    { "status": "delivered", "total": 398 },
    { "status": "unconfirmed", "total": 16 },
    { "status": "failed", "total": 6 }
  ],
  "daily": [
    { "day": "2026-10-21", "total": 14, "sms_total": 14, "whatsapp_total": 0, "delivered": 13, "failed": 0, "unconfirmed": 1, "cost_eur": 1.008, "cost_mad": 11.088 }
  ],
  "error_diagnostics": [],
  "waba_breakdown": []
}
DocsAPI Keys/api-keys
GEThttps://api.envoisms.ma/v1/api-keys
Bearer

Listar claves API

Devuelve las claves API de la cuenta, activas y revocadas, nunca con su valor completo.

Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Permiso de clave API requerido: api_keys.
  • permissions e ip_whitelist son cadenas JSON (ip_whitelist vale null sin restricción).
GEThttps://api.envoisms.ma/v1/api-keys
curl -X GET "https://api.envoisms.ma/v1/api-keys" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY"
{
  "data": [
    {
      "id": "key_e84c2a6b8d0f1e3c5a7b9d2f",
      "name": "Serveur de production",
      "key_prefix": "smr_a8f7d6c5",
      "ip_whitelist": "[\"196.200.1.4\"]",
      "rate_limit": 180,
      "permissions": "[\"send\",\"verify\",\"status\",\"balance\"]",
      "active": 1,
      "last_used_at": "2026-10-22 09:14:03",
      "created_at": "2026-10-01 08:00:00",
      "sandbox": false
    }
  ]
}
DocsAPI Keys/api-keys
POSThttps://api.envoisms.ma/v1/api-keys
Bearer

Crear una clave API

Crea una clave live (smr_…) o sandbox (env_test_…). Responde 201; el valor completo (api_key) se muestra una sola vez.

Cuerpo de la petición (JSON)
CampoTipoEstadoDescripción
namestringOpcionalEtiqueta de la clave (por defecto: "API key").
permissionsstring[]OpcionalEntre send, verify, status, balance, contacts, campaigns, webhooks, billing, analytics, api_keys, optouts, sender_id. Por defecto: send, verify, status, balance. Los valores desconocidos se ignoran.
ip_whiteliststring[]OpcionalÚnicas direcciones IP autorizadas a usar la clave.
rate_limitintegerOpcionalPeticiones por minuto, de 10 a 2000. Por defecto según el plan: Starter 60, Business 180, Pro 500, Enterprise 1 200.
sandboxbooleanOpcionaltrue crea una clave de prueba: las peticiones se validan y registran, no se envía ni se factura nada. El modo de una clave es definitivo.
Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Permiso de clave API requerido: api_keys.
  • El número de claves live activas depende del plan, y las claves sandbox tienen su propio tope; por encima, 403 LIMIT_EXCEEDED. Una clave sandbox añade "note" a la respuesta.
POSThttps://api.envoisms.ma/v1/api-keys
curl -X POST "https://api.envoisms.ma/v1/api-keys" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "OTP backend",
    "permissions": [
      "verify",
      "status"
    ],
    "sandbox": true
  }'
{
  "id": "key_e84c2a6b8d0f1e3c5a7b9d2f",
  "name": "Serveur de production",
  "key_prefix": "smr_a8f7d6c5",
  "api_key": "smr_a8f7d6c5…",
  "permissions": ["send", "verify", "status", "balance"],
  "rate_limit": 180,
  "sandbox": false,
  "warning": "The full API key is shown once. Store it securely."
}
DocsAPI Keys/api-keys/:id
PATCHhttps://api.envoisms.ma/v1/api-keys/:id
Bearer

Modificar una clave API

Modifica el nombre, los permisos, las IP autorizadas, el límite o el estado activo de una clave.

Cuerpo de la petición (JSON)
CampoTipoEstadoDescripción
name | permissions | ip_whitelistmixedOpcionalMismas reglas que al crear.
rate_limitintegerOpcionalPeticiones por minuto, de 10 a 1000.
activebooleanOpcionalfalse suspende la clave.
Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Permiso de clave API requerido: api_keys.
  • El modo sandbox o live no se cambia: 400 SANDBOX_IMMUTABLE. Cree una nueva clave.
PATCHhttps://api.envoisms.ma/v1/api-keys/:id
curl -X PATCH "https://api.envoisms.ma/v1/api-keys/{id}" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "OTP backend (rotated)"
  }'
{
  "updated": true,
  "id": "key_e84c2a6b8d0f1e3c5a7b9d2f"
}
DocsAPI Keys/api-keys/:id
DELETEhttps://api.envoisms.ma/v1/api-keys/:id
Bearer

Revocar una clave API

Revoca definitivamente una clave: deja de autenticar cualquier petición.

Cabeceras
Authorization: Bearer smr_…
Content-Type: application/json
Accept: application/json
A tener en cuenta
  • Permiso de clave API requerido: api_keys.
DELETEhttps://api.envoisms.ma/v1/api-keys/:id
curl -X DELETE "https://api.envoisms.ma/v1/api-keys/{id}" \
  -H "Authorization: Bearer $ENVOISMS_API_KEY"
{
  "revoked": true,
  "id": "key_e84c2a6b8d0f1e3c5a7b9d2f"
}