---
title: "Send messages"
description: "Send one or more SMS or MMS messages in a single request. Each message is validated on its\nown: valid messages are sent, and any that fail are returned in `data.errors` with an\n`error_code` and a human-readable `error_help` that you should log.\n\nRequests must be sent as `application/json`. To send an MMS, include a single image URL in\n`media` and send from an MMS-enabled dedicated number.\n"
version: "v2"
---

> Documentation Index
> Fetch the complete documentation index at: https://dev.touchsms.com.au/llms.txt
> Use this file to discover all available pages before exploring further.

Path: touchSMS API › SMS

`POST /v2/sms`

Send one or more SMS or MMS messages in a single request. Each message is validated on its
own: valid messages are sent, and any that fail are returned in `data.errors` with an
`error_code` and a human-readable `error_help` that you should log.

Requests must be sent as `application/json`. To send an MMS, include a single image URL in
`media` and send from an MMS-enabled dedicated number.

## Authentication

- `basicAuth`, http, header `Authorization`

## Request body

_Required._

- `messages` (`sendSms.messages`; array<object>, required) — Messages to send. MMS messages also go in this array.
  - `to` (`sendSms.messages.to`; string, required) — Recipient number, ideally in E.164 format (`+61412345678`). Local formats are parsed but not recommended.
    - example `"+61412345678"`
  - `from` (`sendSms.messages.from`; string, required) — Sender ID: a dedicated number, verified phone number or verified alphanumeric sender ID on your account. Use E.164 for numbers, or `SHARED_NUMBER` to send from the shared pool.
    - example `"SHARED_NUMBER"`
  - `body` (`sendSms.messages.body`; string, optional) — Message text. Unicode is accepted. Required unless `media` is given.
    - maxLength 1600; example `"Hi Sam, your order has shipped."`
  - `campaign` (`sendSms.messages.campaign`; string, optional) — Campaign name shown in reports. Defaults to `API Campaign (<date>)`.
    - maxLength 190
  - `reference` (`sendSms.messages.reference`; string, optional) — Your reference, included in reports and delivery receipts.
    - maxLength 190
  - `metadata` (`sendSms.messages.metadata`; map<one of>, optional) — Up to 10 key/value pairs. Keys are strings of up to 50 characters; values are strings or integers of up to 100 characters.
    - example `{"order_id":1042,"customer":"C-889"}`
  - `media` (`sendSms.messages.media`; array<string>, optional) — Send as MMS. One publicly accessible image URL (JPEG, PNG or GIF, up to 1MB). MMS must be sent from an MMS-enabled dedicated number and is not supported in every country. Set the MMS subject with the `mms_subject` metadata key (up to 40 characters).
    - format `uri`
  - `date` (`sendSms.messages.date`; string, optional) — Schedule the message for this time (ISO 8601, UTC), for example `2024-03-11T22:52:07Z`.
    - format `date-time`
- `opt_out_list_id` (`sendSms.opt_out_list_id`; integer, optional) — Reject recipients found in this opt-out list with `RECIPIENT_OPTED_OUT`. Cannot be combined with `opt_out_list_name`.
- `opt_out_list_name` (`sendSms.opt_out_list_name`; string, optional) — Name of an existing opt-out list to check recipients against. Cannot be combined with `opt_out_list_id`.
  - maxLength 255
- `reject_duplicates` (`sendSms.reject_duplicates`; boolean, optional) — Drop messages in this request with the same `from`, `to` and `body` as an earlier one.
  - default `false`
- `reject_duplicates_by_recipient` (`sendSms.reject_duplicates_by_recipient`; boolean, optional) — Send only the first message to each recipient in this request.
  - default `false`

## Example requests

### Send an SMS

```json
{
  "messages": [
    {
      "to": "+61412345678",
      "from": "SHARED_NUMBER",
      "body": "Hi Sam, your order has shipped.",
      "reference": "order-1042",
      "metadata": {
        "order_id": 1042
      }
    }
  ]
}
```

### Send an MMS

```json
{
  "messages": [
    {
      "to": "+61412345678",
      "from": "+61491570159",
      "body": "Here is your voucher",
      "metadata": {
        "mms_subject": "Your voucher"
      },
      "media": [
        "https://example.com/voucher.jpg"
      ]
    }
  ]
}
```

### Check recipients against an opt-out list

```json
{
  "opt_out_list_name": "Marketing Opt-Outs",
  "reject_duplicates_by_recipient": true,
  "messages": [
    {
      "to": "+61412345678",
      "from": "SHARED_NUMBER",
      "body": "Spring sale starts today."
    }
  ]
}
```

## Code samples

### cURL

```bash
curl --request POST \
  --url https://app.touchsms.com.au/api/v2/sms \
  --header 'Authorization: Basic <credentials>' \
  --header 'Content-Type: application/json' \
  --data '
{
  "messages": [
    {
      "to": "+61412345678",
      "from": "SHARED_NUMBER",
      "body": "Hi Sam, your order has shipped.",
      "reference": "order-1042",
      "metadata": {
        "order_id": 1042
      }
    }
  ]
}
'
```

### TypeScript

```typescript
const url = 'https://app.touchsms.com.au/api/v2/sms';
const options = {
  method: 'POST',
  headers: {'Content-Type': 'application/json', Authorization: 'Basic <credentials>'},
  body: JSON.stringify({
    messages: [
      {
        to: '+61412345678',
        from: 'SHARED_NUMBER',
        body: 'Hi Sam, your order has shipped.',
        reference: 'order-1042',
        metadata: {order_id: 1042}
      }
    ]
  })
};

fetch(url, options)
  .then(res => res.json())
  .then(json => console.log(json))
  .catch(err => console.error(err));
```

### Python

```python
import requests

url = "https://app.touchsms.com.au/api/v2/sms"

payload = { "messages": [
        {
            "to": "+61412345678",
            "from": "SHARED_NUMBER",
            "body": "Hi Sam, your order has shipped.",
            "reference": "order-1042",
            "metadata": { "order_id": 1042 }
        }
    ] }
headers = {
    "Content-Type": "application/json",
    "Authorization": "Basic <credentials>"
}

response = requests.post(url, json=payload, headers=headers)

print(response.text)
```

## Responses

### 200

Messages processed. Check `data.errors` for any messages that were rejected.

#### Example

```json
{
  "data": {
    "meta": {
      "totalParts": 1,
      "totalMessages": 1,
      "totalCredits": 1,
      "scheduledMessages": 0
    },
    "messages": [
      {
        "status": "VALID",
        "ulid": "01hrq7v3c8w9m2k5t6y4x0z1ab",
        "to": "61412345678",
        "from": "SHARED_NUMBER",
        "body": "Hi Sam, your order has shipped.",
        "campaign": "API Campaign (2024-03-11)",
        "reference": "order-1042",
        "metadata": {
          "order_id": 1042
        },
        "date": null,
        "meta": {
          "size": 31,
          "parts": 1,
          "is_unicode": false,
          "is_mms": false,
          "cost": 1,
          "country": "AU"
        }
      }
    ],
    "errors": [
      {
        "status": "ERROR",
        "error_code": "INVALID_RECIPIENT",
        "error_help": "Recipient number is invalid or could not be parsed.",
        "to": "04123",
        "from": "SHARED_NUMBER",
        "body": "Hi Sam, your order has shipped."
      }
    ]
  }
}
```

- `data` (`sendSms.response.200.data`; object, optional)
  - example `{"meta":{"totalParts":1,"totalMessages":1,"totalCredits":1,"scheduledMessages":0},"messages":[{"status":"VALID","ulid":"01hrq7v3c8w9m2k5t6y4x0z1ab","to":"61412345678","from":"SHARED_NUMBER","body":"Hi Sam, your order has shipped.","campaign":"API Campaign (2024-03-11)","reference":"order-1042","metadata":{"order_id":1042},"date":null,"meta":{"size":31,"parts":1,"is_unicode":false,"is_mms":false,"cost":1,"country":"AU"}}],"errors":[{"status":"ERROR","error_code":"INVALID_RECIPIENT","error_help":"Recipient number is invalid or could not be parsed.","to":"04123","from":"SHARED_NUMBER","body":"Hi Sam, your order has shipped."}]}`
  - `meta` (`sendSms.response.200.data.meta`; object, optional) — Totals for the accepted messages.
    - `totalParts` (`sendSms.response.200.data.meta.totalParts`; integer, optional) — Total SMS parts across all accepted messages.
    - `totalMessages` (`sendSms.response.200.data.meta.totalMessages`; integer, optional)
    - `totalCredits` (`sendSms.response.200.data.meta.totalCredits`; number, optional) — Total cost of the accepted messages.
    - `scheduledMessages` (`sendSms.response.200.data.meta.scheduledMessages`; integer, optional) — Number of accepted messages that are scheduled.
    - `creditBalance` (`sendSms.response.200.data.meta.creditBalance`; number, optional) — Your current credit balance. Dry run only.
    - `hasSufficientCredits` (`sendSms.response.200.data.meta.hasSufficientCredits`; boolean, optional) — Whether your balance covers `totalCredits`. Dry run only.
  - `messages` (`sendSms.response.200.data.messages`; array<object>, optional) — Messages that were accepted.
    - `status` (`sendSms.response.200.data.messages.status`; string, optional)
      - one of `"VALID"`
    - `ulid` (`sendSms.response.200.data.messages.ulid`; string, optional) — Unique message ID, also sent in delivery receipts. Not present on dry runs.
    - `to` (`sendSms.response.200.data.messages.to`; string, optional)
    - `from` (`sendSms.response.200.data.messages.from`; string, optional)
    - `body` (`sendSms.response.200.data.messages.body`; string | null, optional)
    - `campaign` (`sendSms.response.200.data.messages.campaign`; string | null, optional)
    - `campaign_id` (`sendSms.response.200.data.messages.campaign_id`; integer, optional) — Present when the message was linked to an existing campaign.
    - `reference` (`sendSms.response.200.data.messages.reference`; string | null, optional)
    - `metadata` (`sendSms.response.200.data.messages.metadata`; object | null, optional)
    - `date` (`sendSms.response.200.data.messages.date`; string | null, optional) — Scheduled send time, or `null` to send now.
      - format `date-time`
    - `meta` (`sendSms.response.200.data.messages.meta`; object, optional)
      - `size` (`sendSms.response.200.data.messages.meta.size`; integer, optional) — Message length in characters.
      - `parts` (`sendSms.response.200.data.messages.meta.parts`; integer, optional) — Number of SMS parts.
      - `is_unicode` (`sendSms.response.200.data.messages.meta.is_unicode`; boolean, optional)
      - `is_mms` (`sendSms.response.200.data.messages.meta.is_mms`; boolean, optional)
      - `cost` (`sendSms.response.200.data.messages.meta.cost`; number, optional)
      - `country` (`sendSms.response.200.data.messages.meta.country`; string, optional) — ISO country code of the recipient.
  - `errors` (`sendSms.response.200.data.errors`; array<object>, optional) — Messages that were rejected.
    - `status` (`sendSms.response.200.data.errors.status`; string, optional)
      - one of `"ERROR"`
    - `error_code` (`sendSms.response.200.data.errors.error_code`; string, optional) — - `INVALID_BODY`: body is missing or invalid. - `MESSAGE_TOO_LONG`: body is too long. - `INVALID_MEDIA`: MMS media failed validation. - `INVALID_METADATA`: metadata failed validation. - `INVALID_RECIPIENT`: recipient is invalid or could not be parsed, or MMS is not supported to it. - `INVALID_SCHEDULE`: `date` is invalid. - `INVALID_SENDER_ID`: sender ID is invalid. - `UNAPPROVED_SENDER_ID`: sender ID is not approved on your account. - `RECIPIENT_OPTED_OUT`: recipient has opted out. - `DUPLICATE_MESSAGE`: rejected by duplicate prevention.
      - one of `"INVALID_BODY"`, `"MESSAGE_TOO_LONG"`, `"INVALID_MEDIA"`, `"INVALID_METADATA"`, `"INVALID_RECIPIENT"`, `"INVALID_SCHEDULE"`, `"INVALID_SENDER_ID"`, `"UNAPPROVED_SENDER_ID"`, `"RECIPIENT_OPTED_OUT"`, `"DUPLICATE_MESSAGE"`
    - `error_help` (`sendSms.response.200.data.errors.error_help`; string, optional) — Human-readable explanation. Log this.
    - `to` (`sendSms.response.200.data.errors.to`; string, optional)
    - `from` (`sendSms.response.200.data.errors.from`; string, optional)
    - `body` (`sendSms.response.200.data.errors.body`; string, optional)
    - `campaign` (`sendSms.response.200.data.errors.campaign`; string, optional)
    - `reference` (`sendSms.response.200.data.errors.reference`; string, optional)
    - `metadata` (`sendSms.response.200.data.errors.metadata`; object, optional)
    - `media` (`sendSms.response.200.data.errors.media`; array<string>, optional)
    - `date` (`sendSms.response.200.data.errors.date`; string, optional)
      - format `date-time`

### 401

Missing, malformed or invalid API credentials.

#### Example

```json
{
  "message": "Unauthenticated."
}
```

- `message` (`sendSms.response.401.message`; string, required)

### 406

Your account does not have enough credits for the send. No messages are sent. Scheduled
sends are not checked.

#### Example

```json
{
  "message": "Insufficient credits",
  "required": 102,
  "balance": 11,
  "is_postpaid": false
}
```

- `message` (`sendSms.response.406.message`; string, optional)
- `required` (`sendSms.response.406.required`; number, optional) — Amount the send needs.
- `balance` (`sendSms.response.406.balance`; number, optional) — Your current balance.
- `is_postpaid` (`sendSms.response.406.is_postpaid`; boolean, optional)

### 422

The request failed validation. `errors` lists the messages for each invalid field.

#### Example

```json
{
  "message": "The messages field is required.",
  "errors": {
    "messages": [
      "The messages field is required."
    ]
  }
}
```

- `message` (`sendSms.response.422.message`; string, optional)
- `errors` (`sendSms.response.422.errors`; map<array<string>>, optional)

### 429

Rate limit exceeded. Wait before retrying.

#### Example

```json
{
  "message": "Too Many Attempts."
}
```

- `message` (`sendSms.response.429.message`; string, required)


Source: https://dev.touchsms.com.au/api/sms/send/index.md
