---
title: "Validate messages without sending"
description: "Runs the same validation and pricing as [Send messages](#sendSms) but sends nothing. Use it\nto check sender IDs, recipients and message length, and to see how many parts and credits a\nsend will use.\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/dry`

Runs the same validation and pricing as [Send messages](#sendSms) but sends nothing. Use it
to check sender IDs, recipients and message length, and to see how many parts and credits a
send will use.

## Authentication

- `basicAuth`, http, header `Authorization`

## Request body

_Required._

- `messages` (`sendSmsDry.messages`; array<object>, required) — Messages to send. MMS messages also go in this array.
  - `to` (`sendSmsDry.messages.to`; string, required) — Recipient number, ideally in E.164 format (`+61412345678`). Local formats are parsed but not recommended.
    - example `"+61412345678"`
  - `from` (`sendSmsDry.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` (`sendSmsDry.messages.body`; string, optional) — Message text. Unicode is accepted. Required unless `media` is given.
    - maxLength 1600; example `"Hi Sam, your order has shipped."`
  - `campaign` (`sendSmsDry.messages.campaign`; string, optional) — Campaign name shown in reports. Defaults to `API Campaign (<date>)`.
    - maxLength 190
  - `reference` (`sendSmsDry.messages.reference`; string, optional) — Your reference, included in reports and delivery receipts.
    - maxLength 190
  - `metadata` (`sendSmsDry.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` (`sendSmsDry.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` (`sendSmsDry.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` (`sendSmsDry.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` (`sendSmsDry.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` (`sendSmsDry.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` (`sendSmsDry.reject_duplicates_by_recipient`; boolean, optional) — Send only the first message to each recipient in this request.
  - default `false`

## Example request

```json
{
  "messages": [
    {
      "to": "+61412345678",
      "from": "SHARED_NUMBER",
      "body": "Hi Sam, your order has shipped.",
      "campaign": "string",
      "reference": "string",
      "metadata": {
        "order_id": 1042,
        "customer": "C-889"
      },
      "media": [
        "http://example.com"
      ],
      "date": "2019-08-24T14:15:22Z"
    }
  ],
  "opt_out_list_id": 0,
  "opt_out_list_name": "string",
  "reject_duplicates": false,
  "reject_duplicates_by_recipient": false
}
```

## Code samples

### cURL

```bash
curl --request POST \
  --url https://app.touchsms.com.au/api/v2/sms/dry \
  --header 'Authorization: Basic <credentials>' \
  --header 'Content-Type: application/json' \
  --data '
{
  "messages": [
    {
      "to": "+61412345678",
      "from": "SHARED_NUMBER",
      "body": "Hi Sam, your order has shipped.",
      "campaign": "string",
      "reference": "string",
      "metadata": {
        "order_id": 1042,
        "customer": "C-889"
      },
      "media": [
        "http://example.com"
      ],
      "date": "2019-08-24T14:15:22Z"
    }
  ],
  "opt_out_list_id": 0,
  "opt_out_list_name": "string",
  "reject_duplicates": false,
  "reject_duplicates_by_recipient": false
}
'
```

### TypeScript

```typescript
const url = 'https://app.touchsms.com.au/api/v2/sms/dry';
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.',
        campaign: 'string',
        reference: 'string',
        metadata: {order_id: 1042, customer: 'C-889'},
        media: ['http://example.com'],
        date: '2019-08-24T14:15:22Z'
      }
    ],
    opt_out_list_id: 0,
    opt_out_list_name: 'string',
    reject_duplicates: false,
    reject_duplicates_by_recipient: false
  })
};

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/dry"

payload = {
    "messages": [
        {
            "to": "+61412345678",
            "from": "SHARED_NUMBER",
            "body": "Hi Sam, your order has shipped.",
            "campaign": "string",
            "reference": "string",
            "metadata": {
                "order_id": 1042,
                "customer": "C-889"
            },
            "media": ["http://example.com"],
            "date": "2019-08-24T14:15:22Z"
        }
    ],
    "opt_out_list_id": 0,
    "opt_out_list_name": "string",
    "reject_duplicates": False,
    "reject_duplicates_by_recipient": False
}
headers = {
    "Content-Type": "application/json",
    "Authorization": "Basic <credentials>"
}

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

print(response.text)
```

## Responses

### 200

Validation result. Valid messages have no `ulid` because nothing was sent.

#### 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` (`sendSmsDry.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` (`sendSmsDry.response.200.data.meta`; object, optional) — Totals for the accepted messages.
    - `totalParts` (`sendSmsDry.response.200.data.meta.totalParts`; integer, optional) — Total SMS parts across all accepted messages.
    - `totalMessages` (`sendSmsDry.response.200.data.meta.totalMessages`; integer, optional)
    - `totalCredits` (`sendSmsDry.response.200.data.meta.totalCredits`; number, optional) — Total cost of the accepted messages.
    - `scheduledMessages` (`sendSmsDry.response.200.data.meta.scheduledMessages`; integer, optional) — Number of accepted messages that are scheduled.
    - `creditBalance` (`sendSmsDry.response.200.data.meta.creditBalance`; number, optional) — Your current credit balance. Dry run only.
    - `hasSufficientCredits` (`sendSmsDry.response.200.data.meta.hasSufficientCredits`; boolean, optional) — Whether your balance covers `totalCredits`. Dry run only.
  - `messages` (`sendSmsDry.response.200.data.messages`; array<object>, optional) — Messages that were accepted.
    - `status` (`sendSmsDry.response.200.data.messages.status`; string, optional)
      - one of `"VALID"`
    - `ulid` (`sendSmsDry.response.200.data.messages.ulid`; string, optional) — Unique message ID, also sent in delivery receipts. Not present on dry runs.
    - `to` (`sendSmsDry.response.200.data.messages.to`; string, optional)
    - `from` (`sendSmsDry.response.200.data.messages.from`; string, optional)
    - `body` (`sendSmsDry.response.200.data.messages.body`; string | null, optional)
    - `campaign` (`sendSmsDry.response.200.data.messages.campaign`; string | null, optional)
    - `campaign_id` (`sendSmsDry.response.200.data.messages.campaign_id`; integer, optional) — Present when the message was linked to an existing campaign.
    - `reference` (`sendSmsDry.response.200.data.messages.reference`; string | null, optional)
    - `metadata` (`sendSmsDry.response.200.data.messages.metadata`; object | null, optional)
    - `date` (`sendSmsDry.response.200.data.messages.date`; string | null, optional) — Scheduled send time, or `null` to send now.
      - format `date-time`
    - `meta` (`sendSmsDry.response.200.data.messages.meta`; object, optional)
      - `size` (`sendSmsDry.response.200.data.messages.meta.size`; integer, optional) — Message length in characters.
      - `parts` (`sendSmsDry.response.200.data.messages.meta.parts`; integer, optional) — Number of SMS parts.
      - `is_unicode` (`sendSmsDry.response.200.data.messages.meta.is_unicode`; boolean, optional)
      - `is_mms` (`sendSmsDry.response.200.data.messages.meta.is_mms`; boolean, optional)
      - `cost` (`sendSmsDry.response.200.data.messages.meta.cost`; number, optional)
      - `country` (`sendSmsDry.response.200.data.messages.meta.country`; string, optional) — ISO country code of the recipient.
  - `errors` (`sendSmsDry.response.200.data.errors`; array<object>, optional) — Messages that were rejected.
    - `status` (`sendSmsDry.response.200.data.errors.status`; string, optional)
      - one of `"ERROR"`
    - `error_code` (`sendSmsDry.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` (`sendSmsDry.response.200.data.errors.error_help`; string, optional) — Human-readable explanation. Log this.
    - `to` (`sendSmsDry.response.200.data.errors.to`; string, optional)
    - `from` (`sendSmsDry.response.200.data.errors.from`; string, optional)
    - `body` (`sendSmsDry.response.200.data.errors.body`; string, optional)
    - `campaign` (`sendSmsDry.response.200.data.errors.campaign`; string, optional)
    - `reference` (`sendSmsDry.response.200.data.errors.reference`; string, optional)
    - `metadata` (`sendSmsDry.response.200.data.errors.metadata`; object, optional)
    - `media` (`sendSmsDry.response.200.data.errors.media`; array<string>, optional)
    - `date` (`sendSmsDry.response.200.data.errors.date`; string, optional)
      - format `date-time`

### 401

Missing, malformed or invalid API credentials.

#### Example

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

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

### 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` (`sendSmsDry.response.422.message`; string, optional)
- `errors` (`sendSmsDry.response.422.errors`; map<array<string>>, optional)

### 429

Rate limit exceeded. Wait before retrying.

#### Example

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

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


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