---
title: Send SMS
description: POST /v1/sms — send a transactional SMS using a pre-approved template.
---

import Endpoint from '../../../components/Endpoint.astro';

Send a transactional SMS to a phone number in international
format.

<Endpoint method="POST" path="/v1/sms" />

## Request

```bash
curl https://api.zevsend.com/v1/sms \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "to": "+2348012345678",
    "template_id": "tpl_01HXYZ...",
    "variables": {
      "code": "847291"
    }
  }'
```

### Body parameters

| Field             | Type   | Required | Description                                                                                      |
| ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------ |
| `to`              | string | yes      | Recipient phone in E.164 (`+` then country code then number, no spaces or dashes).                |
| `template_id`     | string | yes      | Template public id (`tpl_…`). Must be an `sms` template.                                          |
| `variables`       | object | sometimes| Values for the template's declared `customer.*` variables.                                        |
| `brand_domain_id` | string | no       | Pin the send to a specific verified domain (`dom_…`) for brand variables like `{{brand.name}}`.    |

### Phone format

We accept international format only. Examples:

| Country | Format example          |
| ------- | ----------------------- |
| Nigeria | `+2348012345678`        |
| Kenya   | `+254712345678`         |
| Ghana   | `+233241234567`         |
| USA     | `+12025550100`          |

Sending without the `+` and country code returns `400 invalid_phone`.

## Response

```http
HTTP/1.1 202 Accepted
Content-Type: application/json

{
  "id": "sms_01HXYZ...",
  "to": "+2348012345678",
  "status": "sending",
  "sandbox": false,
  "created_at": "2025-05-24T12:34:56.789Z"
}
```

## Lifecycle

| State       | Meaning                                                                |
| ----------- | ---------------------------------------------------------------------- |
| `queued`    | Persisted but not yet handed off.                                       |
| `sending`   | Handed off to the network; awaiting accept.                             |
| `sent`      | Accepted by the network and in transit.                                 |
| `delivered` | Confirmed at the recipient's handset.                                   |
| `failed`    | Rejected at submit or permanent delivery failure (out-of-window, etc.). |

SMS has no `bounced` or `complained` state. Permanent failures
land on `failed`.

## Length limits

The rendered body is capped at **1600 characters**. Longer
bodies return `400 body_too_long`. Two notes:

- Standard GSM-7 messages are 160 characters per segment.
  Going over 160 splits across segments and is billed per
  segment.
- Templates with lots of Unicode (emoji, non-Latin scripts)
  fall back to UCS-2, which drops the per-segment limit to 70
  characters.

The dashboard preview shows the rendered length so you can
spot accidental segment splits.

## Pricing

SMS is priced per template, in your team's billing currency.
Open the template in the dashboard to see the rate. Each template
carries a default per-send price plus any **per-country
overrides**: sending to a recipient in a country with an
override charges the override rate; everything else uses the
default. The customer-facing rate already accounts for the
underlying carrier and route, so you only ever read one number.

Credit drains the moment the carrier accepts the send. Carrier
rejections never drain credit. See the
[Pricing guide](/guide/pricing#sms) for the full charge cascade
(plan gate, credit check, dispatch, debit).

## Errors

| Status | Code                       | Cause                                                            |
| ------ | -------------------------- | ---------------------------------------------------------------- |
| `400`  | `invalid_phone`            | `to` isn't a valid E.164 number.                                 |
| `400`  | `body_too_long`            | Rendered SMS exceeds 1600 characters.                            |
| `400`  | `template_id_required`     | `template_id` was missing.                                       |
| `404`  | `template_not_found`       | No such template on this team.                                   |
| `409`  | `wrong_template_channel`   | The template is for email or WhatsApp.                           |
| `422`  | `missing_variable`         | A required variable was missing.                                 |
| `422`  | `unknown_variable`         | You passed a variable the template doesn't declare.              |
| `403`  | `no_approved_brand_domain` | You're live but no approved domain exists for the team.          |
| `403`  | `recipient_not_allowed`    | Sandbox-only: recipient hasn't been verified on this team.       |
| `503`  | `no_carrier_available`     | No enabled carrier serves the destination region right now.      |

## Sender ID

A **sender ID** is the label the recipient sees as the "from"
on the message. Every send uses one of three:

1. **Your team-scope sender ID**, when you've registered one
   through the dashboard and it's been approved for the
   destination country.
2. **A ZevSend platform default**, when your team hasn't
   registered a country-matched sender ID yet. This is a
   pre-approved transactional sender we maintain so new teams
   can send OTPs and alerts on day one without waiting for
   their own sender ID to be reviewed.
3. **A network-provided ID**, on routes where the carrier
   appends one regardless of submission.

The platform default lets you ship immediately. Once you want
your own brand in the from field, register a team-scope sender
ID under **Settings → SMS → Sender IDs** in the dashboard.
We'll review and approve it for the countries you select.

## Costs and regions

SMS pricing varies by destination country. The dashboard
**Usage** page shows your per-country breakdown. Some regions
require local sender ID registration before delivery is
allowed — your dashboard shows which sender ID we'll use per
country.