Getting Started

About

Our REST SMS API makes it fast and easy for you to SMS-enable your software, app or website.

API Endpoint

All URLs referenced in this documentation begin with this base URL:

https://api.tm4b.com/v1

Unencrypted HTTP is not supported.

Content Types

As the SMS API is based on JSON, you can expect the Content-Type header on all of our requests and responses to be application/json. We also expect the same from your requests whenever you POST to the SMS API.

If you require XML, please contact support explaining your use case for our consideration.

Authenticating

To authenticate, just provide your API Key in your request headers. If you don't have an account, create one here.

You can provide it as a Authorization: Bearer token:

POST /v1/sms HTTP/1.1
Content-Type: application/json
Authorization: Bearer db955ce8106368e6d21dfa02ac21facd

or as a custom Api-Key header:

POST /v1/sms HTTP/1.1
Content-Type: application/json
Api-Key: db955ce8106368e6d21dfa02ac21facd

Success Handling

If your request succeeded, the response header will have a HTTP 2xx status code and the JSON returned in the response body will have:

  1. an object representing the outcome of your request specific to the request
  2. the Account Object
  3. the Request Object

For example:

Request /v1/sms POST
{
  "messages": [
    {
      "source_address": "GeorgeLucas",
      "destination_address": "+447956218236",
      "content": "There is a civil war between the Galactic Empire and a Rebel Alliance."
    }
  ]
}
Response HTTP 200 OK
{
  "messages": [
    {
      "object_type": "sms",
      "id": "b13ebb9d-f5af-43fb-bff4-8442f410e9d0",
      "source_address": "GeorgeLucas",
      "source_country": null,
      "destination_address": "+447956218236",
      "destination_country": "GB",
      "direction": "outbound",
      "body": "There is a civil war between the Galactic Empire and a Rebel Alliance.",
      "encoding": 0,
      "delivery_state": "ACCEPTED",
      "created": "2018-01-18T17:06:43+00:00",
      "updated": "2018-01-18T17:06:43+00:00",
      "expires": "2018-01-21T17:06:00+00:00",
      "cost_per_fragment": 0.05,
      "fragment_count": 1,
      "total_cost": 0.05,
      "custom_args": null
    }
  ],
  "request": {
    "object_type": "request",
    "id": "504b483e-35ac-42fe-ad94-549b1216e265",
    "sandbox": false,
    "execution_time": 0.08493
  },
  "account": {
    "object_type": "account",
    "account_id": 2232,
    "balance": 158.95,
    "auto_purchase": true
  }
}

Error Handling

If your request failed, you'll receive a non-2xx status code and the response body will contain:

  1. An array of Error Objects for each error in your request.
  2. the Account Object
  3. the Request Object

For example:

Response
{
  "errors": [
    {
      "code": 1005,
      "description": "Payload is malformed",
      "learn_more": "https://www.tm4b.com/sms-api/#error1005"
    }
  ],
  "request": {
    "object_type": "request",
    "id": "504b483e-35ac-42fe-ad94-549b1216e265",
    "sandbox": false,
    "execution_time": 0.00605
  },
  "account": {
    "object_type": "account",
    "account_id": 2232,
    "balance": 6973.465,
    "auto_purchase": false
  }
}

The errors array is made of an array of Error Objects that represent each error that was thrown.

Sandbox

To test your code on a cost-incurring resource without incurring a cost, add "sandbox": true to the main object:

Request
{
    "sandbox": true,
    "messages": [
        {
            "source_address": "GeorgeLucas",
            "content": "There is a civil war between the Galactic Empire and a Rebel Alliance.",
            "destination_address": "+447956218236"
        }
    ]
}

Best Practices

  1. Timestamps are formatted according to ISO 8601.
  2. JSON must be formatted strictly.
  3. Ignore undocumented attributes and the order of document attributes as these may change.

Common Objects

The Account Object

AttributeTypeDescription
account_idintegerThe ID of the account being used.
balanceintegerYour account balance after completion of your request.
auto_purchasebooleanWhether you have auto-purchase setup or not.

For example:

Response
{
  "object_type": "account",
  "account_id": 2232,
  "balance": 7014.073,
  "auto_purchase": false
}

If you do not authenticate, account will be set to null.

If you don't set up auto-purchase, use the Account Object to track your account's balance.

The Request Object

AttributeTypeDescription
idintegerThe unique identifier for your request.
sandboxbooleanWhether you are in the sandbox or not.
execution_timedecimalThe amount of time taken to execute the request.

For example:

Response
{
  "object_type": "request",
  "id": "504b483e-35ac-42fe-ad94-549b1216e265",
  "sandbox": false,
  "execution_time": 0.08022
}

If you need technical support regarding a specific request, please quote the Request ID.

The Error Object

AttributeTypeDescription
codeintegerA unique identifier for the error.
descriptionstringA short explanation of the error.
learn_moreurlAn online resource to get more details about the error.

For example:

Response
{
  "object_type": "error",
  "code": 1005,
  "description": "Payload is malformed",
  "learn_more": "https://www.tm4b.com/sms-api/#error1005"
}

The contents of description and learn_more are subject to continuous change.

The SMS Object

AttributeTypeDescription
idstringThe unique identifier for the SMS.
source_addressstringThe source from which the SMS was sent.
If you sent it, it will be the Sender ID that you specified. If a mobile user sent it, it will be their phone number.
source_countrystringThe country associated with the source_address
This is formated using ISO 3166-1 alpha-2.
destination_addressstringThe destination to which the SMS was sent.
If you sent it, it will be the phone number of the recipient. If a mobile user sent it, it will be your virtual number.
destination_countrystringThe country associated with the destination_address
This is formated using ISO 3166-1 alpha-2.
directionstringWhether it was sent from you to a mobile device or vice versa.
'outbound' means 'sent to a mobile user' and 'inbound' means 'sent from a mobile user'.
bodystringThe message conveyed through the SMS.
encodingintegerThe character encoding of the message body.
8 is Unicode and 0 is GSM 03.38
delivery_statestringThe current delivery state of the SMS.
createdtimestampWhen the SMS was created.
updatedtimestampWhen the SMS was last updated.
expirestimestampWhen the SMS is/was scheduled to expire.
cost_per_fragmentdecimalThe cost of each individual SMS if the message is a long SMS.
fragment_countintegerThe total number of individual SMS if the message is a long SMS.
total_costdecimalThe total cost of the SMS.
custom_argsobjectA JSON object containing a collection of key/value pairs.
This will only exist if you supplied when sending an SMS.

For example:

{
  "id": "b13ebb9d-f5af-43fb-bff4-8442f410e9d0",
  "source_address": "Crawler",
  "source_country": null,
  "destination_address": "+447956218236",
  "destination_country": "GB",
  "direction": "outbound",
  "body": "Luke Skywalker has vanished.",
  "encoding": 0,
  "delivery_state": "ACCEPTED",
  "created": "2018-01-18T19:38:54+00:00",
  "updated": "2018-01-18T19:38:54+00:00",
  "expires": "2018-01-21T19:38:00+00:00",
  "cost_per_fragment": 0.05,
  "fragment_count": 1,
  "total_cost": 0.05,
  "custom_args": null
}

Possible Delivery States

The different values for state are:

StateDescription
REJECTEDFailed local validation.
ACCEPTEDPassed local validation and added to our internal queue.
QUEUEDWaiting to be dispatched for delivery.
SENTTaken out of our internal queue and dispatched for delivery.
DELIVEREDDelivered to the destination handset.
FAILEDCould not be delivered.
EXPIREDHandset remained unavailable for too long.
UNKNOWNFinal delivery state remains unknown.

Sending SMS

Your Request

To send SMS, request https://api.tm4b.com/v1/sms POST with your messages in the request body. You should provide your messages as objects using the mandatory/optional key/value attributes shown below. You should list your message objects as an array that is the value for messages. For example...

Request
{
  "messages": [
    {
      "source_address": "GeorgeLucas",
      "destination_address": "+447956218236",
      "content": "There is a civil war between the Galactic Empire and a Rebel Alliance."
    },
    {
      "source_address": "DarthVader",
      "destination_address": "+447956218236",
      "content": "لوق أنا والدك",
      "expiry": 60
    }
  ]
}

Mandatory Attributes

Attribute Type Description
source_address stringThe Sender ID of your message.
Can be a phone number up to 15 digits long or an alphanumeric string up to 11 characters long.
destination_address stringThe recipient to whom you are sending your message.
Should be in the international format, beginning with '+' and no trailing 0's.
content stringThe body of the message you are sending.
A single SMS is limited to 160 plain English characters or 70 Unicode characters. If your message exceeds this, we will break it up and send as a Long SMS (up to 10 segments long).

Optional Attributes

Attribute Type Description
destination_country alphabeticThe ISO 3166-1 alpha-2 code for the destination country.
If set, we will ensure that your number is formatted and validated according to the country's numbering plan.
retry_period integerThe number of minutes after which the SMS should expire.
If set, should be between 1-4320.
callback_url urlThe URL of a webhook you want us to post delivery state updates to
This is in addition to the webhooks you've saved in the Client Portal (if any).
custom_args objectA JSON object containing a collection of key/value pairs.
Ensure your JSON is strictly formatted.

Our Response

If your request was successful, messages will contain an array of objects representing the outcome of each message that you submitted (in the same order that you submitted them).

If the message was accepted, you will see an sms object. Otherwise, you will see an error object.

Example 1: Simple SMS

Let's send 1 short English SMS to 1 recipient.

Request /v1/sms POST
{
  "messages": [
    {
      "source_address": "GeorgeLucas",
      "destination_address": "+447956218236",
      "content": "There is a civil war between the Galactic Empire and a Rebel Alliance."
    }
  ]
}
Response HTTP 200 OK
{
  "messages": [
    {
      "object_type": "sms",
      "id": "b13ebb9d-f5af-43fb-bff4-8442f410e9d0",
      "source_address": "GeorgeLucas",
      "source_country": null,
      "destination_address": "+447956218236",
      "destination_country": "GB",
      "direction": "outbound",
      "body": "There is a civil war between the Galactic Empire and a Rebel Alliance.",
      "encoding": 0,
      "delivery_state": "ACCEPTED",
      "created": "2018-01-18T17:06:43+00:00",
      "updated": "2018-01-18T17:06:43+00:00",
      "expires": "2018-01-21T17:06:00+00:00",
      "cost_per_fragment": 0.05,
      "fragment_count": 1,
      "total_cost": 0.05,
      "custom_args": null
    }
  ],
  "request": {
    "object_type": "request",
    "id": "504b483e-35ac-42fe-ad94-549b1216e265",
    "sandbox": false,
    "execution_time": 0.08493
  },
  "account": {
    "object_type": "account",
    "account_id": 2232,
    "balance": 158.95,
    "auto_purchase": true
  }
}

Example 2: Bulk SMS

Let's send the same SMS to 2 recipients.

Request /v1/sms POST
{
  "messages": [
    {
      "source_address": "GeorgeLucas",
      "destination_address": "+447711961111",
      "content": "There is a civil war between the Galactic Empire and a Rebel Alliance."
    },
    {
      "source_address": "GeorgeLucas",
      "destination_address": "+97150349030",
      "content": "There is a civil war between the Galactic Empire and a Rebel Alliance."
    }
  ]
}
Response HTTP 200 OK
{
  "messages": [
    {
      "object_type": "sms",
      "id": "b13ebb9d-f5af-43fb-bff4-8442f410e9d0",
      "source_address": "GeorgeLucas",
      "source_country": null,
      "destination_address": "+447711961111",
      "destination_country": "GB",
      "direction": "outbound",
      "body": "There is a civil war between the Galactic Empire and a Rebel Alliance.",
      "encoding": 0,
      "delivery_state": "ACCEPTED",
      "created": "2018-01-18T17:14:45+00:00",
      "updated": "2018-01-18T17:14:45+00:00",
      "expires": "2018-01-21T17:14:00+00:00",
      "cost_per_fragment": 0.05,
      "fragment_count": 1,
      "total_cost": 0.05,
      "custom_args": null
    },
    {
      "object_type": "sms",
      "id": "b13ebb9d-f5af-43fb-bff4-8442f410e9d0",
      "source_address": "GeorgeLucas",
      "source_country": null,
      "destination_address": "+97150349030",
      "destination_country": "AE",
      "direction": "outbound",
      "body": "There is a civil war between the Galactic Empire and a Rebel Alliance.",
      "encoding": 0,
      "delivery_state": "ACCEPTED",
      "created": "2018-01-18T17:14:45+00:00",
      "updated": "2018-01-18T17:14:45+00:00",
      "expires": "2018-01-21T17:14:00+00:00",
      "cost_per_fragment": 0.05,
      "fragment_count": 1,
      "total_cost": 0.05,
      "custom_args": null
    }
  ],
  "request": {
    "object_type": "request",
    "id": "504b483e-35ac-42fe-ad94-549b1216e265",
    "sandbox": false,
    "execution_time": 0.1282
  },
  "account": {
    "object_type": "account",
    "account_id": 2232,
    "balance": 158.75,
    "auto_purchase": true
  }
}

In this example, the messages array contains 2 message objects because you sent 2 separate messages.

Example 3: Long SMS

Let's send a long message to 1 recipient.

Request /v1/sms POST
{
  "messages": [
    {
      "source_address": "Jabba",
      "destination_address": "+447956218236",
      "content": "Han, Han! If only you hadn't had to dump that shipment of spice... you understand I just can't make an exception. Where would I be if every pilot who smuggled for me dumped their shipment at the first sign of an Imperial starship?"
    }
  ]
}
Response HTTP 200 OK
{
  "messages": [
    {
      "object_type": "sms",
      "id": "b13ebb9d-f5af-43fb-bff4-8442f410e9d0",
      "source_address": "Jabba",
      "source_country": null,
      "destination_address": "+447956218236",
      "destination_country": "GB",
      "direction": "outbound",
      "body": "Han, Han! If only you hadn't had to dump that shipment of spice... you understand I just can't make an exception. Where would I be if every pilot who smuggled for me dumped their shipment at the first sign of an Imperial starship?",
      "encoding": 0,
      "delivery_state": "ACCEPTED",
      "created": "2018-01-18T17:21:19+00:00",
      "updated": "2018-01-18T17:21:19+00:00",
      "expires": "2018-01-21T17:21:00+00:00",
      "cost_per_fragment": 0.05,
      "fragment_count": 2,
      "total_cost": 0.1,
      "custom_args": null
    }
  ],
  "request": {
    "object_type": "request",
    "id": "504b483e-35ac-42fe-ad94-549b1216e265",
    "sandbox": false,
    "execution_time": 0.12408
  },
  "account": {
    "object_type": "account",
    "account_id": 2232,
    "balance": 158.3,
    "auto_purchase": true
  }
}

In this example, your message was split into 2 SMS because it exceeded the 160 character limit placed on plain English SMS. The only point of interest are that the fragment_count is now 2 and total_cost has doubled.

Example 4: Rejection

Let's send an SMS to a malformed recipient number.

Request /v1/sms POST
{
  "messages": [
    {
      "source_address": "GeorgeLucas",
      "destination_address": "07956218236",
      "content": "There is a civil war between the Galactic Empire and a Rebel Alliance."
    }
  ]
}
Response HTTP 200 OK
{
  "messages": [
    {
      "error": [
        {
          "object_type": "error",
          "code": 1016,
          "description": "Attribute 'to' is malformed; should begin with a '+' sign.",
          "learn_more": "https://www.tm4b.com/sms-api/#error1016"
        }
      ]
    }
  ],
  "request": {
    "object_type": "request",
    "id": "504b483e-35ac-42fe-ad94-549b1216e265",
    "sandbox": false,
    "execution_time": 0.01432
  },
  "account": {
    "object_type": "account",
    "account_id": 2232,
    "balance": 157.65,
    "auto_purchase": true
  }
}

The HTTP status of the response is 200 because having a problem with a message does not mean there is a problem with the request.

Example 5: Mixed Responses

Let's simultaneously send 1 SMS to a correct number and another to a malformed number.

Request /v1/sms POST
{
  "messages": [
    {
      "source_address": "GeorgeLucas",
      "destination_address": "+447956218236",
      "content": "There is a civil war between the Galactic Empire and a Rebel Alliance."
    },
    {
      "source_address": "GeorgeLucas",
      "destination_address": "07951292422",
      "content": "There is a civil war between the Galactic Empire and a Rebel Alliance."
    }
  ]
}
Response HTTP 200 OK
{
  "messages": [
      {
        "object_type": "sms",
        "id": "b13ebb9d-f5af-43fb-bff4-8442f410e9d0",
        "source_address": "GeorgeLucas",
        "source_country": null,
        "destination_address": "+447956218236",
        "destination_country": "GB",
        "direction": "outbound",
        "body": "There is a civil war between the Galactic Empire and a Rebel Alliance.",
        "encoding": 0,
        "delivery_state": "ACCEPTED",
        "created": "2018-01-18T19:40:06+00:00",
        "updated": "2018-01-18T19:40:06+00:00",
        "expires": "2018-01-21T19:40:00+00:00",
        "cost_per_fragment": 0.05,
        "fragment_count": 1,
        "total_cost": 0.05,
        "custom_args": null
      }
    ],
      "error": [
        {
          "object_type": "error",
          "code": 1016,
          "description": "Attribute 'to' is malformed; should begin with a '+' sign.",
          "learn_more": "https://www.tm4b.com/sms-api/#error1016"
        }
      ]
    }
  ],
  "request": {
    "object_type": "request",
    "id": "504b483e-35ac-42fe-ad94-549b1216e265",
    "sandbox": false,
    "execution_time": 0.0907
  },
  "account": {
    "object_type": "account",
    "account_id": 2232,
    "balance": 157.6,
    "auto_purchase": true
  }
}

As explained in the previous example, it's perfectly normal for a request to be successful as a whole, but for one of the messages to be rejected.

As such, you should iterate through the response messages array and use conditional statements. If you find an sms object, then your message was accepted. If you find a error object, then your message was rejected.

Example 6: Unicode SMS

Let's send an SMS in in the Arabic language.

Request /v1/sms POST
{
  "messages": [
    {
      "source_address": "DarthVader",
      "destination_address": "+447956218236",
      "content": "لوق أنا والدك"
    }
  ]
}
Response HTTP 200 OK
{
  "messages": [
    {
      "object_type": "sms",
      "id": "b13ebb9d-f5af-43fb-bff4-8442f410e9d0",
      "source_address": "DarthVader",
      "source_country": null,
      "destination_address": "+447956218236",
      "destination_country": "GB",
      "direction": "outbound",
      "body": "لوق أنا والدك",
      "encoding": 8,
      "delivery_state": "ACCEPTED",
      "created": "2018-01-18T19:40:44+00:00",
      "updated": "2018-01-18T19:40:44+00:00",
      "expires": "2018-01-21T19:40:00+00:00",
      "cost_per_fragment": 0.05,
      "fragment_count": 1,
      "total_cost": 0.05,
      "custom_args": null
    }
  ],
  "request": {
    "object_type": "request",
    "id": "504b483e-35ac-42fe-ad94-549b1216e265",
    "sandbox": false,
    "execution_time": 0.07646
  },
  "account": {
    "object_type": "account",
    "account_id": 2232,
    "balance": 157.55,
    "auto_purchase": true
  }
}

Nothing fancy! Just be mindful that Unicode messages are limited to 70 characters per SMS.

Example 7: Custom Arguments

Let's send an SMS with some custom arguments.

Request /v1/sms POST
{
  "messages": [
    {
      "source_address": "GeorgeLucas",
      "destination_address": "+447711961111",
      "content": "There is a civil war between the Galactic Empire and a Rebel Alliance.",
      "custom_args":
      {
        "internal_id": "e6d21dfa02ac21facd",
        "customer": "Loyal Company PLC"
      }
    }
  ]
}
Response HTTP 200 OK
{
  "messages": [
    {
      "object_type": "sms",
      "id": "b13ebb9d-f5af-43fb-bff4-8442f410e9d0",
      "source_address": "GeorgeLucas",
      "source_country": null,
      "destination_address": "+447711961111",
      "destination_country": "GB",
      "direction": "outbound",
      "body": "There is a civil war between the Galactic Empire and a Rebel Alliance.",
      "encoding": 0,
      "delivery_state": "ACCEPTED",
      "created": "2018-01-18T19:41:05+00:00",
      "updated": "2018-01-18T19:41:05+00:00",
      "expires": "2018-01-21T19:41:00+00:00",
      "cost_per_fragment": 0.05,
      "fragment_count": 1,
      "total_cost": 0.05,
      "custom_args":
        {
          "internal_id":"e6d21dfa02ac21facd",
          "customer":"Loyal Company PLC"
        }
    }
  ],
  "request": {
    "object_type": "request",
    "id": "504b483e-35ac-42fe-ad94-549b1216e265",
    "sandbox": false,
    "execution_time": 0.08564
  },
  "account": {
    "object_type": "account",
    "account_id": 2232,
    "balance": 157.5,
    "auto_purchase": true
  }
}

Example 8: Short Life SMS

Let's send an SMS that should only be delivered within the next hour.

Request /v1/sms POST
{
  "messages": [
    {
      "source_address": "GeorgeLucas",
      "destination_address": "+447956218236",
      "content": "There is a civil war between the Galactic Empire and a Rebel Alliance.",
      "retry_period": 60
    }
  ]
}
Response HTTP 200 OK
{
  "messages": [
    {
      "object_type": "sms",
      "id": "b13ebb9d-f5af-43fb-bff4-8442f410e9d0",
      "source_address": "GeorgeLucas",
      "source_country": null,
      "destination_address": "+447956218236",
      "destination_country": "GB",
      "direction": "outbound",
      "body": "There is a civil war between the Galactic Empire and a Rebel Alliance.",
      "encoding": 0,
      "delivery_state": "ACCEPTED",
      "created": "2018-01-18T19:51:04+00:00",
      "updated": "2018-01-18T19:51:04+00:00",
      "expires": "2018-01-18T20:51:00+00:00",
      "cost_per_fragment": 0.05,
      "fragment_count": 1,
      "total_cost": 0.05,
      "custom_args": null
    }
  ],
  "request": {
    "object_type": "request",
    "id": "504b483e-35ac-42fe-ad94-549b1216e265",
    "sandbox": false,
    "execution_time": 0.0918
  },
  "account": {
    "object_type": "account",
    "account_id": 2232,
    "balance": 157.4,
    "auto_purchase": true
  }
}

Notice that expires is set to 1 hour after created whilst all of the previous messages was set to 3 days after created.

Querying SMS

Your Request

To query an existing SMS, request https://api.tm4b.com/v1/sms/{id} GET with an empty body.

Our Response

If your request was successful, our response will contain the sms object for the SMS you queried.

Example 1: Outbound SMS

Let's query an SMS that you sent.

Request /v1/sms/b13ebb9d-f5af-43fb-bff4-8442f410e9d0 GET
Response
{
  "sms": {
    "object_type": "sms",
    "id": "b13ebb9d-f5af-43fb-bff4-8442f410e9d0",
    "source_address": "+44768254543",
    "source_country": "GB",
    "destination_address": "+447711161110",
    "destination_country": "GB",
    "direction": "outbound",
    "body": "test",
    "encoding": 0,
    "delivery_state": "DELIVERED",
    "created": "2018-01-18T20:19:21+00:00",
    "updated": "2018-01-18T20:19:00+00:00",
    "expires": "2018-01-21T20:19:00+00:00",
    "cost_per_fragment": 0.05,
    "fragment_count": 1,
    "total_cost": 0.05,
    "custom_args": null
  },
  "request": {
    "object_type": "request",
    "id": "504b483e-35ac-42fe-ad94-549b1216e265",
    "sandbox": false,
    "execution_time": 0.00605
  },
  "account": {
    "object_type": "account",
    "account_id": 2232,
    "balance": 6973.465,
    "auto_purchase": false
  }
}

Example 2: Inbound SMS

Let's query an SMS that you received.

Request /v1/sms/6f7e065c-7ee3-4b58-a379-b43744424d0a GET
Response
{
  "sms": {
    "object_type": "sms",
    "id": "6f7e065c-7ee3-4b58-a379-b43744424d0a",
    "source_address": "+447711961111",
    "source_country": "GB",
    "destination_address": "+447937985895",
    "destination_country": "GB",
    "direction": "inbound",
    "body": "TEST",
    "encoding": 0,
    "status": null,
    "created": "2018-01-02T17:19:58+00:00",
    "updated": null,
    "expires": null,
    "cost_per_fragment": 0,
    "fragment_count": null,
    "total_cost": 0,
    "custom_args": null
  },
  "request": {
    "object_type": "request",
    "id": "504b483e-35ac-42fe-ad94-549b1216e265",
    "sandbox": false,
    "execution_time": 0.00676
  },
  "account": {
    "object_type": "account",
    "account_id": 2232,
    "balance": 6973.465,
    "auto_purchase": false
  }
}

Checking Coverage

Your Request

To query our coverage for a specific country, request https://api.tm4b.com/v1/coverage/{id} GET with an empty body, where id is the country's 2-letter ISO 3166-1 alpha-2 country code.

Our Response

If your request was successful, the response will contain a country, inbound and outbound objects. The inbound object provides boolean values to let you know if we provide Virtual Mobile Numbers / Virtual Land Lines in that country. The outbound object lists all of the networks which we support in that country. Each network is provided as an array as we may provide more network information in the future.

Example 1: Comprehensive

Let's query Australia, where our comprehensive coverage includes Outbound SMS and Inbound SMS (through Virtual Mobile Numbers and Virtual Landlines).

Request /v1/coverage/au GET
Response
{
  "country": {
    "object_type": "country",
    "name": "Australia",
    "iso_alpha_2": "AU",
    "calling_code": "+61"
  },
  "inbound": {
    "virtual_mobile_number": true,
    "virtual_land_line": true
  },
  "outbound": {
    "networks": [
      {
        "name": "Telstra"
      },
      {
        "name": "Vodafone Australia"
      },
      {
        "name": "YES OPTUS (Singtel Optus Ltd)"
      }
    ]
  },
  "request": {
    "object_type": "request",
    "id": "504b483e-35ac-42fe-ad94-549b1216e265",
    "sandbox": false,
    "execution_time": 0.02957
  },
  "account": {
    "object_type": "account",
    "account_id": 2232,
    "balance": 6973.465,
    "auto_purchase": false
  }
}

Example 2: Partial

Let's query the UAE, where our coverage is limited to Outbound SMS.

Request /v1/coverage/ae GET
Response
{
  "country": {
    "object_type": "country",
    "name": "United Arab Emirates",
    "iso_alpha_2": "AE",
    "calling_code": "+971"
  },
  "inbound": null,
  "outbound": {
    "networks": [
      {
        "name": "DU"
      },
      {
        "name": "ETISALAT"
      }
    ]
  },
  "request": {
    "object_type": "request",
    "id": "504b483e-35ac-42fe-ad94-549b1216e265",
    "sandbox": false,
    "execution_time": 0.03394
  },
  "account": {
    "object_type": "account",
    "account_id": 2232,
    "balance": 6973.465,
    "auto_purchase": false
  }
}

Example 3: None

Let's query North Korea, where we do not have any coverage.

Request /v1/coverage/kp GET
Response
{
  "country": {
    "object_type": "country",
    "name": "North Korea",
    "iso_alpha_2": "KP",
    "calling_code": "+850"
  },
  "inbound": null,
  "outbound": null,
  "request": {
    "object_type": "request",
    "id": "504b483e-35ac-42fe-ad94-549b1216e265",
    "sandbox": false,
    "execution_time": 0.03201
  },
  "account": {
    "object_type": "account",
    "account_id": 2232,
    "balance": 6973.465,
    "auto_purchase": false
  }
}

Checking Pricing

Your Request

To query our complete pricing, request https://api.tm4b.com/v1/pricing GET with an empty body.

We will throttle requests for our complete pricing to just twice per day. Do not request it more than that.

To query pricing for a specific country, request https://api.tm4b.com/v1/pricing/{id} GET with an empty body, where id is the country's 2-letter ISO 3166-1 alpha-2 country code.

Our Response

If you are requesting the pricing for one country, our response will contain a country object as well as inbound and outbound objects with their respective pricing.

If you are requesting our complete pricing, our response will be an array of all countries, with the above 3 objects listed for each country.

Example 1: Comprehensive

Let's query Australia, where our comprehensive coverage includes Outbound SMS and Inbound SMS (through Virtual Mobile Numbers and Virtual Landlines).

Request /v1/pricing/au GET
Response
{
  "country": {
    "object_type": "country",
    "name": "Australia",
    "iso_alpha_2": "AU",
    "calling_code": "+61"
  },
  "inbound": {
    "virtual_mobile_number": {
      "setup": 0,
      "per_month": 30,
      "per_sms": 0
    },
    "virtual_land_line": {
      "setup": 0,
      "per_month": 30,
      "per_sms": 0
    }
  },
  "outbound": {
    "setup": 0,
    "per_month": 0,
    "per_sms": 0.055
  },
  "request": {
    "object_type": "request",
    "id": "504b483e-35ac-42fe-ad94-549b1216e265",
    "sandbox": false,
    "execution_time": 0.03024
  },
  "account": {
    "object_type": "account",
    "account_id": 2232,
    "balance": 6973.465,
    "auto_purchase": false
  }
}

Example 2: Partial

Let's query the UAE, where our coverage is limited to Outbound SMS.

Request /v1/pricing/ae GET
Response
{
  "country": {
    "object_type": "country",
    "name": "United Arab Emirates",
    "iso_alpha_2": "AE",
    "calling_code": "+971"
  },
  "inbound": null,
  "outbound": {
    "setup": 0,
    "per_month": 0,
    "per_sms": 0.034
  },
  "request": {
    "object_type": "request",
    "id": "504b483e-35ac-42fe-ad94-549b1216e265",
    "sandbox": false,
    "execution_time": 0.03067
  },
  "account": {
    "object_type": "account",
    "account_id": 2232,
    "balance": 6973.465,
    "auto_purchase": false
  }
}

Example 3: None

Let's query North Korea, where we do not have any coverage.

Request /v1/pricing/kp GET
Response
{
  "country": {
    "object_type": "country",
    "name": "North Korea",
    "iso_alpha_2": "KP",
    "calling_code": "+850"
  },
  "inbound": null,
  "outbound": null,
  "request": {
    "object_type": "request",
    "id": "504b483e-35ac-42fe-ad94-549b1216e265",
    "sandbox": false,
    "execution_time": 0.02905
  },
  "account": {
    "object_type": "account",
    "account_id": 2232,
    "balance": 6973.465,
    "auto_purchase": false
  }
}

Validating Your Account

Your Request

To validate your account independently of other resources, request https://api.tm4b.com/v1/account GET with an empty body.

Our Response

If your request was successful, you'll get the Account Object and the Request Object as usual.

Don't use /account to monitor your balance. Do this in real-time as part of all other requests.

Example

Request /v1/account GET
Response
{
  "request": {
    "object_type": "request",
    "id": "504b483e-35ac-42fe-ad94-549b1216e265",
    "sandbox": false,
    "execution_time": 0.00465
  },
  "account": {
    "object_type": "account",
    "account_id": 2232,
    "balance": 6973.465,
    "auto_purchase": false
  }
}

Webhooks

Introduction

We can provide you with real-time updates for your Outbound SMS and notify you whenever you receive new Inbound SMS. All you have to do is set up scripts on your server that an accept HTTP POST requests and then parse the JSON that we POST to you.

Consuming a Webhook

  1. Provide us with the URL of your webhook endpoint(s).
  2. Listen out for our HTTP POST requests and consume the JSON body.
  3. Confirm receipt with HTTP 200 OK to prevent us from re-POSTing.

Providing URLs

For outbound_sms updates, you can provide message-specific URLs as part of your request using the callback_url attribute. Otherwise, you can set general URLs through the Client Portal.

For inbound_sms updates, you can only set general URLs through the Client Portal.

You can provide up to 3 general URLs by default, with more upon request, and we send your updates to all 3.

You can also view recent webhooks made by our API in the Client Portal.

Error Codes

When you request /sms using HTTP GET, you're requesting the details of an SMS identified by its id in the URI (e.g. /sms/{id}).

Error 1001 means that there is something wrong with the id that you've provided.

It should be a UUID.

Although you requested /sms/{id} using HTTP GET and a well-formed {id}, we could not find an SMS on your account with the {id} you've given.

If you have more than one account, ensure you're using the correct API Key. Otherwise, check our logs to ensure the {id} being sent to us is correct.

Making a HTTP POST request means that you're giving an instruction which should be provided as a set of arguments formatted as a JSON object in the body of your request.

Error 1004 means that the body of your POST request is completely empty.

Making a HTTP POST request means that you're giving an instruction which should be provided as a set of arguments formatted as a JSON object in the body of your request.

Error 1005 means that something is wrong with your JSON object. Either it's not JSON at all or its malformed somehow.

Paste your object into jsonlint.com to find out what's breaking it.

Error 1006 means that you're using HTTP POST on a resource which only supports HTTP GET.

For example, you may be using HTTP POST on /sms/{id}, whilst you can only use it on /sms.

If you're trying to create/send a resource, then stick to HTTP POST and remove the identifier.

If you're trying to pull/get a resource, then use HTTP GET and leave the URI as it is.

We can tell which resource you're requesting because, in the URI, the resource comes right after the version number.

Error 1007 means you've requested a resource that doesn't exist. Most likely, your URI is malformed, so compare it to the list of resources we provide.

To send SMS, you need to provide the details of all your messages as an array of objects that are wrapped into an attribute called messages. This is even the case when sending a single SMS.

But seeing Error 1008 means that we could not detect that attribute.

To use the SMS API, you need to let us know which account you're using and that you have permission to use it.

You do this by generating an API key in the account itself and then presenting it in the headers of your request either as an Authorization: Bearer or as a custom Api-Key header.

If you're seeing Error 1009, you've forgotten to provide the API key in either place (or there's something wrong with your implementation).

The API key is 32 characters long and is made up numbers between 0-9 and letters between a-f.

But if you're seeing Error 1010, it means that you've provided an API key which breaks one or more of these rules.

So check your API key and, if it's correct, make sure that your code is not inadvertently modifying it.

If you're seeing Error 1011, you've provided an API Key that follows the structure of an API key. The only problem is that we could not match it to any of our client accounts.

This should never happen. So, if it's happening to you, please contact us right away.

The content attribute is where you specify the body of the message you're trying to send.

Error 1013 means that, whilst parsing your message object, we were unable to find this attribute. In other words, you're trying to send an empty SMS and we don't dispatch empty messages.

When you provide a recipient's phone number, you have 2 choices. The first is to provide it in its international E.164format (beginning with a '+' sign). The second is to provide it however you like, but to also include the destination_country so we can format it for you.

Error 1016 means that you didn't specify the destination_country, nor did you begin the recipient's phone number with the '+' sign.

But don't rush off to just add it to the existing number. Quite often, if you're pulling numbers from somewhere else, like your database, you'll need to reformat it (as mentioned above). Otherwise, you'll just be adding a '+' sign to a number that is formatted incorrectly. So understand how your data is organised and then choose which option is better for you.

The source_address attribute is where you specify the Sender ID of the message you're trying to send.

Error 1018 means that, whilst parsing your message object, we were unable to find this attribute. In other words, you're trying to send an SMS without a Sender ID and we can't accept such messages.

When you send an SMS, you use the source_address parameter to set the Sender ID.

The Sender ID supports letters, numbers and the "." sign. And Error 1019 means that your Sender ID includes a character that isn't supported.

When you send an SMS, you can set the Sender ID to either be a number (so that recipients can reply), a word or a mix of numbers and letters.

If you set it as a number, you're limited to 15 digits. Otherwise, you're limited to just 11 characters.

Error 1020 means that you set it to a number, but that it had more than 15 digits.

The destination_address attribute is where you specify the recipient of the message you're trying to send.

Error 1021 means that, whilst parsing your message object, we were unable to find this attribute. In other words, you're trying to send an SMS without specifying the recipient and we can't accept such messages.

As much as we'd like to provide our service free of charge, we're not able to!

Error 1022 means that you've tried to make a cost-incurring request with insufficient credit on your balance.

Although your short term solution is to buy credit, your long term solution is to set up auto-purchase so that you never run out of credit again.

Error 1023 means that the API key is valid, but that it's associated with an account that is closed or frozen.

If this is news to you, the contact our support team to discuss your options.

If you want to simulate the SMS sending, you've got to specify "sandbox": true. Otherwise, specify "sandbox": false or exclude the sandbox attribute altogether.

Error 1025 means that you've declared sandbox, but that you've set its value as non-boolean. Maybe you've wrapped boolean value into a string or maybe you've providing something else.

We let you provide custom_args so that you can add relevant meta data to your SMS. We include them when we send you updates and, in the future, we may let you query SMS using them.

But if you want to provide them, you have to provide them as a JSON object containing a collection of key/value pairs.

Error 1026 means that you provided custom_args in a manner that is not a well-formatted JSON object containing key/value pairs.

An SMS can't be delivered to a handset if it's unavailable (e.g. switched off).

In such circumstances, network operators keep trying to deliver it, but there's only so long they'll try. That period is the retry period of the SMS (also know as its 'life') and, unless you tell us otherwise, we tell operators to keep retrying for as long as possible.

To tell us otherwise, you specify the number of minutes you want the SMS to live for using the retry_period attribute and we expect to see it as an integer.

Error 1027 means that you've specified the retry_period attribute, but that the value is not an integer.

An SMS can't be delivered to a handset if it's unavailable (e.g. switched off).

In such circumstances, network operators keep trying to deliver it, but there's only so long they'll try. That period is the retry period of the SMS (also know as its 'life') and, unless you tell us otherwise, we tell operators to keep retrying for as long as possible.

To tell us otherwise, you specify the number of minutes you want the SMS to live for using the retry_period attribute and the most you can set it to is 4321 (which is 60 minutes x 24 hours x 3 days).

Error 1029 means that you specified an retry_period value greater than 4321.

To ensure data privacy, the SMS API is only served over HTTPS.

Error 1046 means that you requested "http" instead of "https". Just fix that and all should be well.

If you make a HTTP Post request, you must set the Content-Type to be application/json.

Error 1047 means that you set it to something else.

Error 1048 means that you didn't specify the API version in the URI that you requested.

Most likely, the URI is malformed as a whole. So take a look at your URI and ensure it matches the examples in our documentation.

You indicate which resource you are requesting in the URI of your request.

Error 1049 means that you specified a version, but not the resource.

Most likely, the URI is malformed as a whole. So take a look at your URI and ensure it matches the examples in our documentation.

At the moment, the SMS API only has 1 version which is... "v1".

Error 1050 means that you specified something other than v1 as the version in the URI that you're requesting.

Most likely, the URI is malformed as a whole. So take a look at your URI and ensure it matches the examples in our documentation.

You can send up to 100 messages in one request and Error 1051 means that you've exceeded that limit.

When you send an SMS, you can set the Sender ID to either be a number (so that recipients can reply), a word or a mix of numbers and letters.

If you include any letters, the length of the Sender ID gets limited to just 11 characters.

Error 1054 means that your Sender ID included a letter, but that it had more than 11 characters.

If you make a HTTP Post request, you must set the Content-Type to be application/json.

Error 1055 means we could not find any Content-Type.

When you request coverage using HTTP GET, you're requesting the coverage of a specific country identified by the country's id in the URI (e.g. /coverage/{id}). We expect you to provide it in ISO 3166-1 alpha-2 format.

Error 1057 means that the id you've provided has non-letters in it.

When you request coverage using HTTP GET, you're requesting the coverage of a specific country identified by its id in the URI (e.g. /coverage/{id}). We expect you to provide it in ISO 3166-1 alpha-2 format.

Error 1058 means that the id you've provided is too short. It should be 2 characters long.

When you request coverage using HTTP GET, you're requesting the coverage of a specific country identified by its id in the URI (e.g. /coverage/{id}). We expect you to provide it in ISO 3166-1 alpha-2 format.

Error 1059 means that the id you've provided is too long. It should be 2 characters long.

When you request coverage using HTTP GET, you're requesting the coverage of a specific country. You specify that country by including its ISO 3166-1 alpha-2 code in the URI. For example, /coverage/gb.

Error 1060 means that we could not detect a country id value in the URI.

If you're seeing Error 1061, you've provided a valid API Key. However, it has been deactivated in your Client Portal. So you should either reactivate it or generate a new API Key.

The callback_url can only be the URL of a live website.

You provide the destination_country to help us validate and format your numbers more effectively. So, if you tell us that a message is destined for the United Kingdom, for example, we can ensure that it is valid for mobile numbers in the UK and formatted accordingly. As a result, we can pass malformed numbers and reject invalid numbers, saving you from wasted money.


But if you're seeing error 1064, it means that the destination_country you've provided is invalid.


It should be formatted in the ISO 3166-1 alpha-2 format.

The recipient's number was invalid. So we rejected the message to save you from wasting money on a message that was otherwise destined to fail.

Check the number to ensure it is accurate.

If you're seeing this error, there's a high chance that you tried to send a single SMS without wrapping it inside a numeric index array.


To explain, the API handles multiple SMS by default and you send multiple SMS by compiling the details of each SMS into its own object and then listing the objects as a numeric index array.


If you just want to send 1 SMS, you still need to provide it as an object within the numeric index array - even if it's the only message.

If you're seeing this error, you've tried to send an SMS with a Sender ID of a recognised brand.


If you do represent the brand, please contact us so that we can whitelist it for you.


If you do not, please note that attempting to mislead others as to the identity of the sender or the origin or contents of a message is against our Acceptable Use Policy and may lead to the termination of your account.

All of our resources only accept HTTP POST and HTTP GET.

So, either you're using another method that we don't support, or you're using HTTP POST on a resource that only supports HTTP GET (or vice versa).

Your OTP should not be any shorter than 6 digits long.

Your OTP should not be any longer than 12 digits long.

If you want to customise the message of your 1-Time Password, your customised message needs to include %code% as a placeholder for where you want the verification code to go.

If you're seeing this error, you've tried to customise the message but forgot to include the placeholder.

You've requested /otp using HTTP GET. However, you have not specified an identifier.

If you are trying to create an OTP, please use HTTP POST. Otherwise, please add an identifier to your URI.

Although you requested /otp/{id} using HTTP GET and a well-formed {id}, we could not find an OTP on your account with the {id} you've given.

If you have more than one account, ensure you're using the correct API Key. Otherwise, check our logs to ensure the {id} being sent to us is correct.

You've request an OTP, but the otp_code you've given is not correct.

Notify your user that their OTP verification has failed and be on the watch for repeated OTP verification failures from the same user.

You cannot verify an OTP which has already expired.

If you're not sure how much time you had to verify the OTP, query the OTP again without otp_code and reference otp_maximum_retry_period

You are trying to verify an OTP more times than the OTP allows.

If you're not sure how many times you were allowed to try, query the OTP again without otp_code and reference otp_maximum_retry_count

If you want to specify the maximum_attempt_count, you must provide it as an integer.

To ensure that users cannot force their way through your OTPs, the default maximum_attempt_count is set to 3.

If you want to give them more chances than that, you're free to do so. However, we don't allow more than 10 in any circumstance.

If you're seeing this error, you've specified a maximum_attempt_period in excess of 10 minutes.

If you want to specify the maximum_attempt_count, you must provide it as an integer.

If you want to customise the message of your 1-Time Password, your customised message needs to include %brand% as a placeholder for where you want your brand name to go.

If you're seeing this error, you've tried to customise the message but forgot to include the placeholder.