EnvoiSMS API API

Send SMS and WhatsApp messages and verify phone numbers, as JSON over HTTPS.

Operations 82

POST /messages Send a message #
GET /messages List messages #
POST /messages/bulk Send bulk messages #
GET /messages/{id} Get message status #
POST /messages/typing WhatsApp typing indicator #
POST /verify/send Send OTP verification code #
POST /verify/resend Resend the code of an existing verification session #
POST /verify/check Check OTP code #
POST /verify/lookup Validate phone number (EnvoiSMS Lookup Engine) #
GET /verify/{session_id} Get OTP verification session #
GET /lookup/{number} Number lookup #
GET /hlr/{number} HLR lookup #
GET /conversations List WhatsApp conversations #
GET /conversations/{phone} Get conversation history #
POST /conversations/{phone}/messages Send live chat reply #
GET /whatsapp/profile Get WhatsApp Business profile #
GET /qualified-leads List qualified leads #
GET /inbound-webhooks List Inbound Webhooks #
POST /inbound-webhooks Create an Inbound Webhook #
GET /contacts List contacts #
POST /contacts Create or update contact #
POST /contacts/import Import contacts #
GET /contacts/lists List contact lists #
POST /contacts/lists Create contact list #
DELETE /contacts/{id} Delete contact #
GET /campaigns List campaigns #
POST /campaigns Create campaign #
GET /campaigns/{id} Get campaign #
PATCH /campaigns/{id} Update a campaign #
DELETE /campaigns/{id} Cancel a campaign #
POST /campaigns/{id}/send Send campaign #
GET /optouts List opt-outs #
POST /optouts Create opt-out #
DELETE /optouts/{phone} Remove opt-out #
GET /consents List consent events #
POST /consents Record a consent event #
GET /consents/{phone} Consent state and history for a number #
GET /webhooks List webhooks #
POST /webhooks Create webhook #
PATCH /webhooks/{id} Update webhook #
DELETE /webhooks/{id} Disable webhook #
POST /webhooks/{id}/rotate-secret Rotate webhook signing secret #
POST /webhooks/{id}/test Test webhook #
GET /webhooks/logs List webhook delivery attempts #
POST /webhooks/logs/{log_id}/replay Resend a logged webhook delivery #
GET /billing/balance Get billing balance #
GET /balance Get account balance (alias) #
GET /billing/transactions List billing transactions #
GET /billing/plans List plan pricing #
GET /billing/packs List prepaid SMS packs #
GET /billing/payment-methods List configured payment methods #
POST /billing/topups Create balance top-up or SMS pack #
POST /billing/topups/claim Claim paid top-ups #
GET /billing/quota Get billing quota limit #
PATCH /billing/quota Update billing quota limit #
GET /analytics Get usage analytics #
GET /api-keys List API keys #
POST /api-keys Create API key #
PATCH /api-keys/{id} Update API key #
DELETE /api-keys/{id} Revoke API key #
GET /health Health check #
GET /search Natural language search #
GET /recommend Get system recommendations #
PATCH /qualified-leads/{id} Update a qualified lead #
DELETE /inbound-webhooks/{id} Delete an inbound webhook #
POST /verify/void Cancel a verification #
POST /campaigns/{id}/pause Pause a campaign #
POST /campaigns/{id}/resume Resume a campaign #
POST /contacts/lists/{id} Rename a contact list #
DELETE /contacts/lists/{id} Delete a contact list #
GET /templates List templates #
POST /templates Create a template #
GET /templates/{id} Get a template #
PATCH /templates/{id} Edit a template #
DELETE /templates/{id} Delete a template #
POST /templates/{id}/submit-meta Submit a WhatsApp template to Meta #
POST /templates/sync Sync template statuses from Meta #
GET /sender-ids List Sender ID registrations #
POST /sender-ids Register a Sender ID #
GET /whatsapp/waba-connection Get your WhatsApp connection #
GET /machine-payments/status Machine payments status #
POST /moderation/appeals Appeal a content refusal #

Work with this as data

Every API here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for apis

7 MCP tools reach this
  • find_apisBrowse and filter every API in the catalog.
  • get_api_artifactsOne API's artifacts, grouped by type.
  • get_openapiThe primary OpenAPI for this API.
  • find_similar_apisAPIs that look like this one.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This API
curl "https://apis.io/api/v1/apis/envoisms-ma:envoisms-api-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Discovery needs no key. Ratings and market analysis are Pro.

Get an API key

Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.

A second provider on the same verified email joins the account you already have.

OpenAPI Specification

envoisms-ma-api-openapi.json Raw ↑
{
  "openapi": "3.0.3",
  "info": {
    "title": "EnvoiSMS API",
    "x-service-info": {
      "title": "EnvoiSMS API",
      "description": "Telecom messaging and phone verification APIs for Morocco",
      "categories": [
        "telecom",
        "messaging",
        "sms",
        "otp"
      ]
    },
    "description": "Send SMS and WhatsApp messages and verify phone numbers, as JSON over HTTPS.\nGuides, copy-paste samples (cURL, Node.js, Python, PHP) and the full error\ncatalogue: https://envoisms.ma/en/docs/\n\n## Quickstart\n```bash\nexport ENVOISMS_API_KEY=env_test_...   # a sandbox key: nothing is sent or charged\ncurl https://api.envoisms.ma/v1/messages \\\n  -H \"Authorization: Bearer $ENVOISMS_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"to\": \"+212612345678\", \"message\": \"Your order #1042 has shipped.\"}'\n```\n\n## Authentication\nSend `Authorization: Bearer <key>` on every request. Create keys in the\ndashboard (Dashboard → API keys); a key is shown once, when it is created.\n\n- `smr_...` — a **live** key: messages are sent and charged.\n- `env_test_...` — a **sandbox** key: every request is validated exactly as\n  a live one, but nothing is sent and nothing is charged. `POST /messages`\n  returns `sandbox: true` and `simulated_cost` (the price a live key would\n  pay); `POST /verify/send` returns `sandbox_code`. Each key sees only its\n  own kind of traffic.\n\n**Sandbox test numbers** (`POST /messages`, sandbox keys only):\n`+212600000002` fails (`message.failed`, `SANDBOX_TEST_FAILED`),\n`+212600000003` is undeliverable (`message.undeliverable`),\n`+212600000004` is opted out (`403 OPTED_OUT` at request time),\n`+212600000005` never gets a receipt; every other number is delivered\n(`message.delivered`) about 2 seconds after the send.\n\nEach key carries a list of permissions; a call outside them returns\n`403 FORBIDDEN`. Each operation below states the permission it needs. An\noptional IP allow-list per key turns a call from any other IP into\n`401 UNAUTHORIZED`.\n\nThe WhatsApp setup routes (`/whatsapp/...`: connecting numbers, profile,\ntools, relay settings) change state only from a dashboard session; an API\nkey may read `GET /whatsapp/waba-connection` and `GET /whatsapp/profile`.\n\n## Base URL\n```\nhttps://api.envoisms.ma/v1\n```\n`GET /health` is served from the API root (`https://api.envoisms.ma/health`).\n\n## Rate limiting\nLimits apply per API key over a one-minute window. A new key gets its\nplan's default — Starter 60, Business 180, Pro 500, Enterprise 1,200\nrequests/minute — adjustable per key.\n\n| Header | Meaning |\n|---|---|\n| `X-RateLimit-Limit` | The key's requests/minute allowance |\n| `X-RateLimit-Remaining` | Requests left in the current window |\n| `X-RateLimit-Reset` | Unix time (seconds) when the window resets |\n\nOver the limit the API returns `429 RATE_LIMITED` with `Retry-After`\n(seconds). Honor it — retrying earlier only consumes the next window.\n\n## Idempotency\n`POST /messages`, `POST /messages/bulk` and their `/waba/messages`\nequivalents accept an `Idempotency-Key` header (up to 255 characters, e.g.\na UUID). Retry a timed-out or failed call with the **same key**: the send\nhappens at most once, and the retry returns the original JSON body with\n`Idempotency-Replayed: true`.\n\n- Keys are scoped to your account and kept for 24 hours.\n- Every response below 500 is stored and replayed — **including 4xx**\n  (e.g. `402`, `429`). After a 4xx, fix the cause and use a **new** key.\n  A 5xx is not stored: retry it with the same key.\n- The same key with a **different** body returns `422 IDEMPOTENCY_KEY_REUSED`.\n- While the original request still runs, a retry returns\n  `409 IDEMPOTENCY_IN_FLIGHT` with `Retry-After`.\n\n## Pagination\nLists take `limit` and `offset` and return\n`{\"data\": [...], \"limit\", \"offset\", \"total\"}`. Each list states its\ndefault and maximum `limit`.\n\n## Versioning and deprecation\nThe version is in the path (`/v1`). Within `/v1` we only make additive\nchanges: new endpoints, new optional request fields, new response fields,\nnew error codes and new webhook event types. Write clients that ignore\nfields they do not know.\n\nA breaking change ships as a new major path (`/v2`) beside `/v1`, never in\nplace. Retiring a `/v1` endpoint, field or behaviour follows this notice:\n\n- At least **12 months** between the announcement and the date it stops\n  working (the sunset), announced by e-mail to the account owner, in the\n  changelog and on the endpoint itself.\n- Every response to a deprecated endpoint carries\n  `Deprecation: @<unix-seconds>` (RFC 9745, when it was deprecated),\n  `Sunset: <HTTP-date>` (RFC 8594, when it stops working) and\n  `Link: <url>; rel=\"deprecation\"` pointing at the migration guide.\n  Browsers can read them (`Access-Control-Expose-Headers`).\n- Security fixes are the one exception: an endpoint that cannot be made\n  safe may be changed or withdrawn sooner, with notice as early as we can\n  give it.\n\nNo `/v1` endpoint is deprecated today.\n\n## Webhooks\nRegister an HTTPS endpoint with `POST /webhooks`; the response carries its\nsigning secret, shown once (`POST /webhooks/{id}/rotate-secret` issues a\nnew one). Each delivery is a JSON `POST`:\n\n```json\n{\"event\": \"message.delivered\", \"data\": {\"id\": \"msg_...\", \"to\": \"+212612345678\", \"channel\": \"sms\", \"status\": \"delivered\", \"metadata\": {}}, \"timestamp\": \"2026-10-22T10:30:04.000Z\"}\n```\n\nEvents: `message.sent`, `message.delivered`, `message.read`,\n`message.failed`, `message.undeliverable`, `message.fallback`,\n`message.inbound`, `message.reaction`, `contact.optout`, `contact.optin`,\n`lead.qualified`. Subscribe to an exact name, a namespace (`message.*`) or\n`*`. A message status carries `{id, to, channel, status, error_code?,\nerror_message?, metadata?}`.\n\n**Signature.** Verify `X-EnvoiSMS-Signature-256: t=<unix-seconds>,v1=<hex>`:\ncompute `HMAC-SHA256(secret, \"{t}.\" + raw_request_body)` (hex), compare it\nto `v1` in constant time, and reject when `|now - t|` exceeds 5 minutes.\nThe older `X-EnvoiSMS-Signature: sha256=<hex>` (HMAC of the body alone, no\ntimestamp) is still sent for existing integrations. The event name is also\nin `X-EnvoiSMS-Event`.\n\n**Retries.** A `5xx`, a `429` or a network error is retried after 1 minute,\n10 minutes and 1 hour (4 attempts). Any other `4xx` is not retried.\n`GET /webhooks/logs` lists recent attempts and\n`POST /webhooks/logs/{log_id}/replay` sends one again. Answer `2xx` within\n10 seconds and de-duplicate on `data.id` + `event`.\n\n**WhatsApp relay.** A connected WhatsApp number set to *webhook* mode in the\ndashboard relays its messages and statuses to your URL in WhatsApp Cloud's\nnative shape, signed with `X-Hub-Signature-256: sha256=<hex>` —\n`HMAC-SHA256(webhook_secret, raw_request_body)` with that number's secret,\nshown once in the dashboard when it is created or rotated. Without a secret\nnothing is relayed.\n\n## Errors\n```json\n{\n  \"error\": {\n    \"code\": \"INVALID_PHONE\",\n    \"message\": \"to must be E.164 format, for example +212612345678\",\n    \"docs\": \"https://envoisms.ma/en/docs#errors\"\n  }\n}\n```\n\n`code` is the stable, machine-readable key — branch on it, never on the\nmessage text. `message` is an English sentence saying what went wrong and\nwhat to do; its wording may improve. Every code an API key can receive,\ngrouped and with what to do, is at https://envoisms.ma/en/docs/#errors.\nThe most common:\n\n| Code | HTTP | What to do |\n|---|---|---|\n| `UNAUTHORIZED` | 401 | Send a valid key as `Authorization: Bearer <key>` (also: IP not on the key's allow-list) |\n| `FORBIDDEN` | 403 | Add the permission the operation needs to the key |\n| `INVALID_JSON` / `MISSING_FIELD` / `INVALID_FIELD` | 400 | Fix the body; the message names the field |\n| `INVALID_PHONE` | 400 | Send E.164, e.g. `+212612345678` |\n| `INSUFFICIENT_BALANCE` | 402 | Top up; nothing was charged |\n| `OPTED_OUT` | 403 | The recipient unsubscribed (STOP); do not resend |\n| `WHATSAPP_NOT_CONNECTED` | 403 | Connect a WhatsApp Business number, or omit `channel` to send SMS |\n| `OUT_OF_24H_WINDOW` | 400 | Send an approved `template`, or an SMS; nothing was charged |\n| `SENDER_ID_GENERIC` / `SENDER_ID_PROTECTED_BRAND` | 400 / 403 | Use your own brand name as `sender_id`, or omit it |\n| `CONTENT_REFUSED` / `CONTENT_BLOCKED` / `SPAM_OR_PHISHING_DETECTED` | 400 | Content refused; appeal with `POST /moderation/appeals` and `error.appeal` |\n| `MISSING_TEMPLATE_PARAMS` | 400 | Supply the listed values in `template_variables` |\n| `CONSENT_ATTESTATION_REQUIRED` | 400 | WhatsApp marketing campaigns need `consent_attested: true` |\n| `TEMPLATE_PENDING_REVIEW` / `TEMPLATE_REJECTED_BY_REVIEW` | 409 | Wait for the EnvoiSMS review, or edit the template to resubmit |\n| `INVALID_CODE` / `MAX_ATTEMPTS` / `SESSION_NOT_FOUND` | 400 / 429 / 404 | OTP: wrong code, too many attempts, or session expired — start a new one |\n| `OTP_COOLDOWN` / `OTP_RATE_LIMIT` / `OTP_COUNTRY_PAUSED` | 429 | Wait `Retry-After` |\n| `RATE_LIMITED` / `QUOTA_EXCEEDED` | 429 | Wait `Retry-After` / your daily quota resets at 00:00 UTC |\n| `IDEMPOTENCY_IN_FLIGHT` / `IDEMPOTENCY_KEY_REUSED` | 409 / 422 | Retry with the same key / use a new key |\n| `INTERNAL_ERROR` | 500 | Retry with backoff (same `Idempotency-Key`); contact support if it persists |\n\nTreat an unknown code as a failure of that request, not of your\nintegration.\n\n### WhatsApp failure codes\nA WhatsApp message that WhatsApp refuses ends `failed` with one of these\n`error_code` values (on `GET /messages/{id}` and in the `failed` webhook),\neach with an English `error_message`:\n\n| Code | WhatsApp code | Meaning | Automatic retry |\n|---|---|---|---|\n| `META_WINDOW_EXPIRED` | 131047 | 24-hour customer service window closed — send a template | no |\n| `META_UNDELIVERABLE` | 131026 | Number not reachable on WhatsApp | never |\n| `META_MARKETING_LIMIT` | 131049 | Per-user marketing message limit (ecosystem protection) — do not resend immediately | never |\n| `META_PAIR_RATE_LIMITED` | 131056 | Too many messages to this contact in a short time | yes |\n| `META_RATE_LIMITED` | 130429 | Throughput limit of the sending number | yes |\n| `META_POLICY_BLOCKED` | 368 | Sending number temporarily blocked for policy violations | never |\n| `META_ACCOUNT_LOCKED` | 131031 | WhatsApp Business account locked | no |\n| `META_PAYMENT_ISSUE` | 131042 | Payment-method problem on the WhatsApp Business account | no |\n| `META_UNSUPPORTED_TYPE` | 131051 | Message type not supported | no |\n| `META_TEMPLATE_PARAM_MISMATCH` | 132000 | Template variable count does not match | no |\n| `META_TEMPLATE_NOT_FOUND` | 132001 | Template missing in that language, or not approved | no |\n| `META_TEMPLATE_PARAM_FORMAT` | 132012 | Template variable in the wrong format | no |\n| `META_PHONE_NOT_REGISTERED` | 133010 | Sending number not registered | no |\n| `META_GENERIC_ERROR` / `META_SERVICE_UNAVAILABLE` | 131000 / 131016 | Temporary WhatsApp-side failure | yes |\n| `META_<code>` | other | Any other WhatsApp code, with WhatsApp's own description | no |\n\n\"yes\" = re-sent automatically with a growing delay, three attempts in\ntotal, before the message fails. \"never\" = retrying would only hurt the\nsending number's quality, so it is not retried even on a server error. A\nmessage refused before WhatsApp accepted it is refunded automatically.\n",
    "version": "1.0.0",
    "contact": {
      "name": "EnvoiSMS support",
      "email": "contact@envoisms.ma",
      "url": "https://envoisms.ma/support"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://envoisms.ma/en/legal/terms/"
    }
  },
  "servers": [
    {
      "url": "https://api.envoisms.ma/v1",
      "description": "Production (live and sandbox keys)"
    }
  ],
  "security": [
    {
      "BearerAuth": []
    }
  ],
  "components": {
    "securitySchemes": {
      "BearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Your EnvoiSMS API key"
      }
    },
    "schemas": {
      "ConsentEvent": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "cns_9f2a..."
          },
          "phone": {
            "type": "string",
            "example": "+212612345678"
          },
          "channel": {
            "type": "string",
            "enum": [
              "whatsapp",
              "sms",
              "any"
            ]
          },
          "purpose": {
            "type": "string",
            "enum": [
              "marketing",
              "transactional",
              "otp",
              "service"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "granted",
              "withdrawn"
            ]
          },
          "source": {
            "type": "string",
            "enum": [
              "api",
              "form",
              "import",
              "dashboard",
              "whatsapp_keyword",
              "sms_keyword",
              "whatsapp_inbound",
              "ctwa"
            ]
          },
          "evidence": {
            "type": "object",
            "nullable": true
          },
          "recorded_at": {
            "type": "string",
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Message": {
        "type": "object",
        "description": "Response to POST /messages — the message as accepted into the send queue.",
        "properties": {
          "id": {
            "type": "string",
            "example": "msg_8f2da1b2c3d4e5f6a7b8c9d0"
          },
          "to": {
            "type": "string",
            "example": "+212612345678"
          },
          "channel": {
            "type": "string",
            "enum": [
              "sms",
              "whatsapp",
              "voice",
              "rcs"
            ],
            "description": "sms is generally available with multi-operator routing. whatsapp sends from your own connected WhatsApp Business number (templates, text, media, interactive messages, reactions, contacts, locations) via the official WhatsApp Cloud API. voice and rcs are available on request."
          },
          "cascade": {
            "type": "boolean"
          },
          "status": {
            "type": "string",
            "example": "queued"
          },
          "cost": {
            "type": "object",
            "description": "What was actually charged. Always zero on a sandbox key.",
            "properties": {
              "eur": {
                "type": "number",
                "example": 0.072
              },
              "mad": {
                "type": "number",
                "example": 0.79
              }
            }
          },
          "segments": {
            "type": "integer",
            "example": 1
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "sandbox": {
            "type": "boolean",
            "description": "Present and true only when the request used an `env_test_` key. Nothing was delivered and nothing was charged."
          },
          "simulated_cost": {
            "type": "object",
            "description": "Sandbox only. What this exact send would have cost on a live key, at the account's own rates — so an integration can be costed before it goes live. Reported separately from `cost` so a rehearsal can never be mistaken for revenue.\n",
            "properties": {
              "eur": {
                "type": "number",
                "example": 0.072
              },
              "mad": {
                "type": "number",
                "example": 0.79
              }
            }
          },
          "warnings": {
            "type": "array",
            "description": "Sandbox only. What a live key would have been refused for — today `OUT_OF_24H_WINDOW` for a free-form WhatsApp message outside the customer service window. The sandbox still simulates the send.\n",
            "items": {
              "type": "object",
              "properties": {
                "code": {
                  "type": "string",
                  "example": "OUT_OF_24H_WINDOW"
                },
                "message": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "MessageRecord": {
        "type": "object",
        "description": "A stored message as returned by GET /messages/{id}. The metadata object provided at send time is not echoed here; it travels in DLR webhooks.",
        "properties": {
          "id": {
            "type": "string",
            "example": "msg_8f2da1b2c3d4e5f6a7b8c9d0"
          },
          "campaign_id": {
            "type": "string",
            "nullable": true
          },
          "to": {
            "type": "string",
            "example": "+212612345678"
          },
          "channel": {
            "type": "string",
            "enum": [
              "sms",
              "whatsapp",
              "telegram",
              "voice",
              "rcs"
            ]
          },
          "body": {
            "type": "string",
            "example": "Votre code est 1234"
          },
          "sender_id": {
            "type": "string",
            "example": "MonApp"
          },
          "unicode": {
            "type": "integer",
            "description": "1 when the body required the UCS-2 alphabet (Arabic etc.), 0 for GSM-7"
          },
          "segments": {
            "type": "integer",
            "example": 1
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "sent",
              "delivered",
              "failed",
              "undeliverable",
              "unconfirmed"
            ],
            "description": "unconfirmed = the network accepted the message but never reported a final outcome within the reporting window; a delivery receipt arriving later still replaces it."
          },
          "error_code": {
            "type": "string",
            "nullable": true,
            "description": "Set on failed/undeliverable messages. A short diagnostic key: DLR_<network status> when the failure came from a delivery report (e.g. DLR_REJECTD, DLR_EXPIRED, DLR_UNDELIV), GATEWAY_* for a failure on the EnvoiSMS routing side before the network was reached, ENVOISMS_* for other routing diagnostics, META_* for a WhatsApp refusal (see \"WhatsApp failure codes\" in the API description). Treat unknown values as a generic terminal failure — new keys may appear."
          },
          "error_message": {
            "type": "string",
            "nullable": true,
            "description": "Human-readable failure reason in English, set together with error_code. When the network gave no reason, this reads \"The destination network did not deliver this message and gave no reason.\" Display text — do not parse it programmatically; branch on status and error_code instead."
          },
          "cost_eur": {
            "type": "number",
            "example": 0.072
          },
          "cost_mad": {
            "type": "number",
            "example": 0.79
          },
          "scheduled_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "sent_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "delivered_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "read_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Timestamp when the recipient opened and read the message (WhatsApp blue checkmark)"
          },
          "failed_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "operator": {
            "type": "string",
            "nullable": true,
            "example": "Maroc Telecom",
            "description": "Resolved destination network for Moroccan numbers (Maroc Telecom, Orange, Inwi, Autre); best-effort elsewhere."
          },
          "sandbox": {
            "type": "integer",
            "description": "1 when the message was created by an env_test_ sandbox key"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "SendMessageRequest": {
        "type": "object",
        "required": [
          "to"
        ],
        "description": "`message` is required for SMS and for WhatsApp text and interactive messages. A WhatsApp message whose content is a template, a media object, a reaction, contacts, a location or a sticker needs no `message` (for media it becomes the caption when given). Exactly one of reaction, contacts, location or sticker per message, and none of them together with another content type.\n",
        "properties": {
          "to": {
            "type": "string",
            "description": "Destination phone in E.164 format",
            "example": "+212612345678"
          },
          "message": {
            "type": "string",
            "description": "Message content (max 1600 chars). See the object description for when it may be omitted on WhatsApp.",
            "example": "Bonjour! Votre commande #1234 est confirmée."
          },
          "from": {
            "type": "string",
            "description": "SMS sender shown to the recipient (3–11 letters, digits or spaces; a generic header such as INFO or a bank name is refused). Omitted, the SMS goes out under the shared sender EnvoiSMS at 0.792 MAD. Your own name works with no registration at 1.70 MAD until it is validated with your RC, then at the standard rate. Aliases: sender_id, sender.",
            "example": "MonApp"
          },
          "channel": {
            "type": "string",
            "enum": [
              "sms",
              "whatsapp",
              "voice",
              "rcs"
            ],
            "default": "sms",
            "description": "Channel for message delivery: sms (default) for Moroccan operator routes; whatsapp to send from your own connected WhatsApp Business number (403 WHATSAPP_NOT_CONNECTED otherwise). To send an ordinary text to a customer who happens to use WhatsApp, sms is enough: no connection, no template, no 24-hour window. On whatsapp, a message that is not a template may only be sent to a contact who wrote to your number in the last 24 hours — otherwise it is refused with 400 OUT_OF_24H_WINDOW before any charge (with a cascade, WhatsApp is skipped and the next channel is used instead)."
          },
          "cascade": {
            "type": "boolean",
            "description": "When true, tries WhatsApp, then falls back to SMS: at once if WhatsApp refuses the message or reports it failed (not on WhatsApp, 24-hour window closed, ...), or when no delivery report arrives within cascade_timeout (120 s by default). Only a send that actually went out is billed: a refused or failed WhatsApp message is not, and you pay for the SMS alone. A WhatsApp message still waiting for its delivery report did go out (it can still be delivered when the phone reconnects), so it is billed along with its SMS fallback, and refunded if WhatsApp later reports it failed. Tip: give your WhatsApp template a time-to-live (TTL) no longer than cascade_timeout, so a phone that comes back online does not receive both.",
            "default": false
          },
          "cascade_timeout": {
            "type": "integer",
            "minimum": 30,
            "maximum": 43200,
            "default": 120,
            "description": "With cascade: seconds to wait for a delivery report before the next channel. Shorter falls back faster but bills more double sends; longer means fewer duplicates. Out of range: 400 INVALID_FIELD."
          },
          "metadata": {
            "type": "object",
            "description": "Custom key-value pairs, stored with the message and returned in DLR webhooks. One key carries platform semantics: set purpose: \"otp\" when the message carries a one-time code you generated yourself — if the network reports a hard delivery failure on a Moroccan number while the code is still fresh (within 10 minutes), the platform automatically re-sends it once over an alternate SMS route, under the same message id and at no extra charge. Prefer /verify/send when you want the whole code lifecycle (generation, delivery cascade, validation, expiry) managed for you. Set no_failover: true to opt out of this automatic re-send entirely — the first delivery attempt's outcome is then final.",
            "example": {
              "order_id": "ORD-12345",
              "customer_ref": "C-789"
            }
          },
          "buttons": {
            "type": "array",
            "description": "Optional WhatsApp interactive buttons (max 3). Only supported on the whatsapp channel.",
            "items": {
              "$ref": "#/components/schemas/MessageButton"
            }
          },
          "interactive": {
            "type": "object",
            "description": "WhatsApp Cloud API interactive message payload. cta_url: action {name: \"cta_url\", parameters: {display_text (max 20 chars), url}}. location_request_message: body text plus an optional action {name: \"send_location\"}; the contact answers with a location.",
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "list",
                  "button",
                  "product",
                  "product_list",
                  "flow",
                  "carousel",
                  "order_details",
                  "cta_url",
                  "location_request_message"
                ]
              },
              "header": {
                "type": "object"
              },
              "body": {
                "type": "object",
                "properties": {
                  "text": {
                    "type": "string"
                  }
                }
              },
              "footer": {
                "type": "object",
                "properties": {
                  "text": {
                    "type": "string"
                  }
                }
              },
              "action": {
                "type": "object"
              }
            }
          },
          "template": {
            "type": "object",
            "description": "WhatsApp only. An approved template of your account; exempt from the 24-hour window. Priced by the template's category as registered on your account.",
            "properties": {
              "name": {
                "type": "string",
                "example": "order_update"
              },
              "language": {
                "type": "string",
                "example": "fr"
              },
              "components": {
                "type": "array",
                "items": {
                  "type": "object"
                }
              }
            }
          },
          "phone_number_id": {
            "type": "string",
            "description": "WhatsApp only. Which of your connected numbers sends; defaults to your default number."
          },
          "image": {
            "$ref": "#/components/schemas/WhatsAppMedia"
          },
          "video": {
            "$ref": "#/components/schemas/WhatsAppMedia"
          },
          "audio": {
            "$ref": "#/components/schemas/WhatsAppMedia"
          },
          "document": {
            "$ref": "#/components/schemas/WhatsAppMedia"
          },
          "sticker": {
            "$ref": "#/components/schemas/WhatsAppMedia"
          },
          "reaction": {
            "type": "object",
            "description": "WhatsApp only. React to a message of the conversation. Not billed. An empty emoji removes your reaction.",
            "required": [
              "message_id",
              "emoji"
            ],
            "properties": {
              "message_id": {
                "type": "string",
                "description": "WhatsApp message id (wamid) of the message you react to",
                "example": "wamid.HBgLMjEyNjEyMzQ1Njc4FQIAEhgg"
              },
              "emoji": {
                "type": "string",
                "example": "👍"
              }
            }
          },
          "contacts": {
            "type": "array",
            "maxItems": 20,
            "description": "WhatsApp only. Contact cards (WhatsApp Cloud API contacts object). name.formatted_name is required; allowed keys: name, phones, emails, urls, addresses, org, birthday (YYYY-MM-DD).",
            "items": {
              "type": "object",
              "required": [
                "name"
              ],
              "properties": {
                "name": {
                  "type": "object",
                  "required": [
                    "formatted_name"
                  ],
                  "properties": {
                    "formatted_name": {
                      "type": "string",
                      "example": "Service client"
                    },
                    "first_name": {
                      "type": "string"
                    },
                    "last_name": {
                      "type": "string"
                    }
                  }
                },
                "phones": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "phone": {
                        "type": "string",
                        "example": "+212522000000"
                      },
                      "type": {
                        "type": "string",
                        "example": "WORK"
                      },
                      "wa_id": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          },
          "location": {
            "type": "object",
            "description": "WhatsApp only. Send a location pin.",
            "required": [
              "latitude",
              "longitude"
            ],
            "properties": {
              "latitude": {
                "type": "number",
                "minimum": -90,
                "maximum": 90,
                "example": 33.5731
              },
              "longitude": {
                "type": "number",
                "minimum": -180,
                "maximum": 180,
                "example": -7.5898
              },
              "name": {
                "type": "string",
                "example": "Agence Casablanca"
              },
              "address": {
                "type": "string",
                "example": "Bd Zerktouni, Casablanca"
              }
            }
          },
          "context": {
            "type": "object",
            "description": "WhatsApp only. Reply to (quote) an earlier message of the conversation. Works with every type except reaction.",
            "required": [
              "message_id"
            ],
            "properties": {
              "message_id": {
                "type": "string",
                "example": "wamid.HBgLMjEyNjEyMzQ1Njc4FQIAEhgg"
              }
            }
          }
        }
      },
      "WhatsAppMedia": {
        "type": "object",
        "description": "WhatsApp media by exactly one of id (a media id uploaded to WhatsApp) or link (an https URL). caption on image, video and document only; filename on document only.",
        "properties": {
          "id": {
            "type": "string"
          },
          "link": {
            "type": "string",
            "example": "https://example.com/brochure.pdf"
          },
          "caption": {
            "type": "string",
            "maxLength": 1024
          },
          "filename": {
            "type": "string"
          }
        }
      },
      "BulkSendRequest": {
        "type": "object",
        "required": [
          "messages"
        ],
   

# --- truncated at 32 KB (259 KB total) ---
# Full source: https://artifacts.apis.io/envoisms-ma/openapi/_original/envoisms-ma-api-openapi.json